Compare commits
29 Commits
feat/Delet
...
b9dc631bf7
| Author | SHA1 | Date | |
|---|---|---|---|
| b9dc631bf7 | |||
| 6425496d2e | |||
| fd5a01c67f | |||
| 29e80ce296 | |||
| c244a308d7 | |||
| a25d68f527 | |||
| 77b54aebf0 | |||
| 840436eb36 | |||
| f5dd7534f0 | |||
| ebb9de5294 | |||
| 531d140b99 | |||
| 97b8e8aa7d | |||
| 4ed998e937 | |||
| ad0f0336d5 | |||
| 7cde4fa138 | |||
| 1b7259d1c1 | |||
| 09ff812562 | |||
| e7c631d532 | |||
| 06573e37d7 | |||
| d740f4da44 | |||
| f24bd38ec7 | |||
| 45934baf0a | |||
| 4384ee53c5 | |||
| d89c326be5 | |||
| 2dccb4f871 | |||
| a9ddf3074e | |||
| db087bb184 | |||
| 5e9083041f | |||
| be0555d6bc |
@ -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/**
|
||||||
|
|||||||
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
所有重要的项目变更都将记录在此文件中。
|
所有重要的项目变更都将记录在此文件中。
|
||||||
|
|
||||||
|
## [1.0.12] - 2026-03-06
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- 支持 AskUserQuestion 多问题和多选功能
|
||||||
|
|
||||||
## [1.0.9] - 2026-03-04
|
## [1.0.9] - 2026-03-04
|
||||||
|
|
||||||
### 优化
|
### 优化
|
||||||
|
|||||||
42
docs/code-to-chat-feature.md
Normal 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"
|
||||||
|
}
|
||||||
|
```
|
||||||
55
docs/webpack-optimization.md
Normal 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
@ -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` 文件
|
||||||
|
- 等待安装完成
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**方式 B:通过扩展视图**
|
||||||
|
- 点击左侧活动栏的扩展图标(或按 `Ctrl+Shift+X`)
|
||||||
|
- 点击扩展视图右上角的 `...` (更多操作)
|
||||||
|
- 选择 `从 VSIX 安装...`
|
||||||
|
- 选择 `iccoder-Trial-1.0.vsix` 文件
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**方式 C:通过命令行**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
code --install-extension iccoder-Trial-1.0.vsix
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **重启 VS Code**
|
||||||
|
- 安装完成后,建议重启 VS Code 以确保插件正常加载
|
||||||
|
|
||||||
|
#### 步骤 2:打开 IC Coder 界面
|
||||||
|
|
||||||
|
**登录后会自动打开**,手动打开也有以下几种方式:
|
||||||
|
|
||||||
|
**方式 1:通过侧边栏**
|
||||||
|
|
||||||
|
- 点击左侧活动栏的 IC Coder 图标
|
||||||
|
- 侧边栏会显示 IC Coder 聊天界面
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**方式 2:通过命令面板**
|
||||||
|
|
||||||
|
- 按 `Ctrl+Shift+P` (Windows/Linux) 或 `Cmd+Shift+P` (macOS)
|
||||||
|
- 输入以下命令之一:
|
||||||
|
- `IC Coder: 打开聊天` - 打开聊天界面
|
||||||
|
- `打开 IC Coder 助手` - 打开助手面板
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 步骤 3:开始使用
|
||||||
|
|
||||||
|
插件已预配置好后端服务,安装后即可直接使用,无需手动配置。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 故障排除
|
||||||
|
|
||||||
|
#### 问题 :插件无法安装
|
||||||
|
|
||||||
|
**症状**:安装 VSIX 文件时报错
|
||||||
|
|
||||||
|
#### 解决方案:
|
||||||
|
|
||||||
|
- 确认 VS Code 版本 >= 1.60.0
|
||||||
|
|
||||||
|
- 检查 VSIX 文件是否完整(未损坏)
|
||||||
|
|
||||||
|
- 尝试以管理员权限运行 VS Code
|
||||||
|
|
||||||
|
- 清除 VS Code 缓存后重试
|
||||||
|
|
||||||
|
### 完整使用流程示例
|
||||||
|
|
||||||
|
下面通过一个完整的案例,展示如何使用 IC Coder 从需求到代码生成的全过程。
|
||||||
|
|
||||||
|
#### 步骤 1:输入设计需求
|
||||||
|
|
||||||
|
在对话框中输入设计需求,例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
我需要设计一个 8 位加法器,要求有进位输入和进位输出
|
||||||
|
```
|
||||||
|
|
||||||
|
点击**发送**按钮。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 步骤 2:AI 询问补充信息
|
||||||
|
|
||||||
|
AI 会根据需求,询问一些关键的设计细节。例如:
|
||||||
|
|
||||||
|
- 是否需要溢出检测?
|
||||||
|
- 时钟频率要求是多少?
|
||||||
|
- 是否需要流水线设计?
|
||||||
|
|
||||||
|
您只需要根据实际需求**选择相应的选项或者直接输入需求**即可,AI 会根据您的选择生成最合适的设计方案。
|
||||||
|
|
||||||
|
#### 步骤 3:确认 AI 生成的任务列表
|
||||||
|
|
||||||
|
AI 会根据您的需求和补充信息,生成一个详细的任务列表(Todo List)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
仔细查看任务列表,确认无误后点击**确认**按钮,AI 将开始执行。
|
||||||
|
|
||||||
|
#### 步骤 4:观察 AI 执行过程
|
||||||
|
|
||||||
|
AI 开始工作后,您可以在对话框中实时看到所有执行步骤:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
每个步骤完成后,任务列表中对应的项目会被标记为完成状态。
|
||||||
|
|
||||||
|
#### 步骤 5:仿真运行与结果查看
|
||||||
|
|
||||||
|
当 AI 完成代码生成后,会自动运行仿真验证:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 步骤 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. **需提前打开一个文件夹作为工作区**,否则会准确的为您服务
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. **开箱即用**
|
||||||
|
- 插件已预配置后端服务,无需手动设置
|
||||||
|
- 安装后即可直接使用所有功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**祝您使用愉快!如有问题欢迎反馈。**
|
||||||
BIN
media/manual/仿真运行结果.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
media/manual/侧边栏打开.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
media/manual/命令面板打开.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
media/manual/安装方式1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
media/manual/安装方式2.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
media/manual/打开文件夹.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
media/manual/确认任务.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
media/manual/聊天界面.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
media/manual/观察执行过程.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
media/manual/输入需求.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
24
package.json
@ -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": {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
211
src/extension.ts
@ -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 })
|
|
||||||
.then((session) => {
|
|
||||||
if (session) {
|
|
||||||
vscode.commands.executeCommand("ic-coder.openChat");
|
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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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");
|
|
||||||
const userInfo = getCachedUserInfo();
|
|
||||||
|
|
||||||
if (userInfo?.isPluginTrial === true) {
|
|
||||||
// 试用用户,跳过邀请码验证,直接返回已验证
|
|
||||||
console.log("[ICHelperPanel] 试用用户,跳过邀请码验证");
|
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "invitationCodeStatus",
|
command: "invitationCodeStatus",
|
||||||
verified: true,
|
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,
|
|
||||||
} = require("../services/invitationService");
|
|
||||||
const result = await InvitationService.verifyCode(message.code);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
// 验证成功,保存状态
|
|
||||||
await InvitationService.saveVerificationStatus(
|
|
||||||
context,
|
|
||||||
message.code,
|
|
||||||
);
|
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "invitationCodeVerified",
|
command: "invitationCodeVerified",
|
||||||
success: true,
|
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,
|
||||||
|
|||||||
181
src/panels/UserManualPanel.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/providers/codeActionProvider.ts
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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: () => {
|
||||||
|
|||||||
@ -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: [],
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 提交错误结果
|
// 提交错误结果
|
||||||
|
try {
|
||||||
const result = createBusinessErrorResult(callId, errorMessage);
|
const result = createBusinessErrorResult(callId, errorMessage);
|
||||||
|
console.log(`[ToolExecutor] 准备提交错误结果: ${toolName}, callId=${callId}`);
|
||||||
await submitToolResult(result);
|
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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取工作目录
|
// 获取工作目录
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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 || ""}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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 : "未知错误"}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
// 隐藏状态栏
|
// 隐藏状态栏
|
||||||
@ -379,18 +404,21 @@ async function handleUserMessageWithBackend(
|
|||||||
// 发送系统通知 - 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 +430,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 +486,7 @@ async function handleUserMessageWithBackend(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mode,
|
mode,
|
||||||
serviceTier // 传递服务等级
|
serviceTier, // 传递服务等级
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -470,7 +498,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 +568,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 +576,7 @@ export async function handlePlanAction(
|
|||||||
"planTitle:",
|
"planTitle:",
|
||||||
planTitle,
|
planTitle,
|
||||||
"serviceTier:",
|
"serviceTier:",
|
||||||
serviceTier
|
serviceTier,
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@ -564,7 +592,7 @@ export async function handlePlanAction(
|
|||||||
`请按照刚才的计划执行:${planTitle}`,
|
`请按照刚才的计划执行:${planTitle}`,
|
||||||
extensionPath,
|
extensionPath,
|
||||||
"agent",
|
"agent",
|
||||||
serviceTier
|
serviceTier,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -581,7 +609,7 @@ export async function handlePlanAction(
|
|||||||
`请根据以下建议修改计划:${modification}`,
|
`请根据以下建议修改计划:${modification}`,
|
||||||
extensionPath,
|
extensionPath,
|
||||||
"plan",
|
"plan",
|
||||||
serviceTier
|
serviceTier,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -636,7 +664,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 +681,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 +697,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 +746,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 +762,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 +775,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 +811,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 +820,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 +902,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 +926,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 +945,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 +960,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 +971,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 +987,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 +996,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 +1007,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 +1018,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 +1026,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 +1038,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 +1057,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 +1102,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 +1129,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 +1204,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 +1241,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 +1264,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 +1278,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 +1310,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 +1318,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 +1334,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 +1344,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 +1352,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 +1368,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 +1379,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 +1407,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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件信息
|
* 获取文件信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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'>
|
||||||
|
|||||||
@ -34,14 +34,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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -165,41 +157,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 +165,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);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,11 +46,22 @@ export function getFilePathTagScript(): string {
|
|||||||
return `
|
return `
|
||||||
// 处理文件路径标签点击
|
// 处理文件路径标签点击
|
||||||
function handleFilePathClick(filePath) {
|
function handleFilePathClick(filePath) {
|
||||||
|
// 解析文件路径,支持 file.v:5-8 格式
|
||||||
|
const match = filePath.match(/^(.+?):(\\d+)-(\\d+)$/);
|
||||||
|
if (match) {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
command: 'openFile',
|
command: 'openFilePathTag',
|
||||||
|
filePath: match[1],
|
||||||
|
startLine: parseInt(match[2]),
|
||||||
|
endLine: parseInt(match[3])
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'openFilePathTag',
|
||||||
filePath: filePath
|
filePath: filePath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 创建文件路径标签
|
// 创建文件路径标签
|
||||||
window.createFilePathTag = function(filePath) {
|
window.createFilePathTag = function(filePath) {
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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>
|
||||||
<div class="select-dropdown" id="modelDropdown">
|
<span class="tooltiptext">IC Coder自研FPGA专属微调模型</span>
|
||||||
<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>
|
|
||||||
<span class="tooltiptext">选择模型</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';
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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">
|
||||||
@ -903,6 +929,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 +943,7 @@ export function getWebviewContent(
|
|||||||
|
|
||||||
${getMessageAreaScript()}
|
${getMessageAreaScript()}
|
||||||
${getAgentCardScript()}
|
${getAgentCardScript()}
|
||||||
|
${getMoreOptionsComponentScript()}
|
||||||
${getWaveformPreviewScript()}
|
${getWaveformPreviewScript()}
|
||||||
${getConversationHistoryBarScript()}
|
${getConversationHistoryBarScript()}
|
||||||
${getProgressBarScript()}
|
${getProgressBarScript()}
|
||||||
|
|||||||
@ -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 ];
|
||||||