5 Commits

Author SHA1 Message Date
6acec9fcb5 refactor: 重构消息路由模块,添加个人规则管理功能
- 拆分消息处理逻辑到独立的 helper 模块
   - 新增个人规则的增删改查路由处理
   - 优化代码结构,提升可维护性
2026-03-17 15:39:02 +08:00
9d273fff83 Merge branch 'feat/codeToChat' into feat/personalRules 2026-03-17 14:38:49 +08:00
76c1af6e7e refactor: 优化错误提示信息
- 统一错误提示为用户友好的消息
- 调整代码风格保持一致性
2026-03-17 10:39:29 +08:00
eb345e3e1f feat:个人规则删除二次确认功能 2026-03-07 16:11:20 +08:00
8751944053 feat: 添加个人规则功能
- 新增个人规则管理模块 (personalRulesManager.ts)
   - 支持创建、编辑、删除多条规则
   - 规则存储在用户目录 ~/.iccoder/rules/
   - 对话时自动将规则传递给后端
   - 添加后端对接文档和 webpack 优化指南
2026-03-07 15:13:54 +08:00
17 changed files with 2811 additions and 1090 deletions

View File

@ -37,51 +37,38 @@ IC Coder Plugin 目前支持:
### 2.1 核心目标 ### 2.1 核心目标
- **前端提供原子工具**:前端只提供独立的 Vivado 命令工具,不控制流程 - **前端工具封装**:在插件前端实现 Vivado 调用的完整逻辑
- **后端AI控制流程**所有执行顺序、依赖检查由后端AI决策 - **后端简化调用**:后端只需调用一个工具接口
- **工具职责单一**:每个工具只负责执行一个具体命令 - **文件自动导入**Vivado 执行完成后,自动将产出文件导入到项目
- **结果透明返回**:执行结果完整返回给后端,由后端决定下一步 - **流程可视化**:执行进度、日志实时显示
### 2.2 设计原则 ### 2.2 非功能目标
- 前端不做流程判断,只执行命令 - 配置简单,用户友好
- 前端不检查依赖关系,由后端保证顺序 - 执行过程可视化(进度、日志)
- 前端返回详细的执行结果,包括成功/失败、输出、报告等 - 错误处理完善,提示清晰
- 后端AI根据结果智能决策是否继续
## 3. 功能详细需求 ## 3. 功能详细需求
### 3.1 前端提供的工具 ### 3.1 Vivado 支持的操作
前端提供 4 个独立的工具,每个工具只负责执行一个命令: #### 3.1.1 综合Synthesis
#### 3.1.1 createVivadoProject - 创建工程 - **输入**Verilog/VHDL 源文件、约束文件(.xdc
- **输出**:设计检查点(.dcp、综合报告.rpt
- **用途**:将 RTL 代码转换为门级网表,检查资源使用情况
- **输入**:项目名称、芯片型号、源文件列表、约束文件(可选 #### 3.1.2 实现Implementation
- **输出**:工程文件(.xpr
- **说明**:创建 Vivado 工程,不执行任何构建操作
#### 3.1.2 runVivadoSynthesis - 综合 - **输入**:综合后的 .dcp 文件
- **输出**:实现后的 .dcp 文件、时序报告、布局布线报告
- **用途**:完成布局布线,检查时序是否满足要求
- **输入**:工程路径或源文件、芯片型号、顶层模块、约束文件(可选 #### 3.1.3 生成比特流Generate Bitstream
- **输出**.dcp 文件、综合报告
- **说明**:执行综合,前端不检查工程是否存在。约束文件在此阶段可选,主要用于时序约束
#### 3.1.3 runVivadoImplementation - 实现 - **输入**:实现后的 .dcp 文件
- **输出**:比特流文件(.bit
- **输入**综合后的 .dcp 文件路径、约束文件(必需,包含管脚约束) - **用途**生成可烧录到 FPGA 的配置文件
- **输出**:实现后的 .dcp 文件、时序报告
- **说明**:执行实现,前端不检查 .dcp 是否存在。**管脚约束是必需的**,否则无法完成布局布线
#### 3.1.4 runVivadoBitstream - 生成比特流
- **输入**:实现后的 .dcp 文件路径
- **输出**.bit 文件(可下载到 FPGA 的配置文件)
- **核心依赖**
1. 实现已完成
2. 工程指定目标芯片型号
3. 已完成管脚约束(无管脚约束无法生成)
- **说明**:生成比特流,前端不检查 .dcp 是否存在
### 3.2 配置管理 ### 3.2 配置管理
@ -115,111 +102,90 @@ IC Coder Plugin 目前支持:
### 3.3 工具调用接口 ### 3.3 工具调用接口
#### 3.3.1 通用响应格式 #### 3.3.1 接口定义
所有工具返回统一的响应格式:
```typescript ```typescript
interface VivadoToolRequest {
command: string; // 命令类型synthesis | implementation | bitstream
parameters?: {
topModule?: string; // 顶层模块名
files?: string[]; // 输入文件列表
part?: string; // FPGA 型号(可选,使用配置中的默认值)
constraints?: string; // 约束文件路径(.xdc
outputDir?: string; // 输出目录
};
importOutput?: {
enabled: boolean; // 是否自动导入
targetDir: string; // 目标目录
};
}
interface VivadoToolResponse { interface VivadoToolResponse {
success: boolean; // 是否成功 success: boolean;
command: string; // 执行的命令 command: string;
executionTime: number; // 执行时间(毫秒) executionTime: number; // 执行时间(毫秒)
output: string; // 完整输出日志 output: string; // 标准输出
error?: string; // 错误信息(如果失败) error?: string; // 错误信息
outputFiles?: string[]; // 产出文件路径列表 importedFiles?: string[]; // 已导入的文件列表
reports?: { reports?: {
resources?: string; // 资源使用摘要 // 报告摘要
timing?: string; // 时序信息摘要 resources?: string; // 资源使用情况
timing?: string; // 时序信息
}; };
} }
``` ```
#### 3.3.2 各工具的参数定义 ### 3.4 执行流程
**createVivadoProject** #### 3.4.1 参数验证
```typescript
{ - 检查 Vivado 是否已配置
projectName: string; // 项目名称 - 检查可执行文件是否存在
part: string; // 芯片型号 - 检查输入文件是否存在
topModule: string; // 顶层模块 - 检查工作目录是否存在
files: string[]; // 源文件列表
constraints?: string; // 约束文件(可选) #### 3.4.2 TCL 脚本生成
mode: 'gui' | 'batch'; // 执行模式
} 根据命令类型自动生成 TCL 脚本:
**综合脚本示例synth.tcl**
```tcl
# 读取源文件
read_verilog counter.v
read_xdc constraints.xdc
# 设置顶层模块
set_property top counter [current_fileset]
# 综合
synth_design -part xc7a35tcpg236-1 -top counter
# 生成报告
report_utilization -file utilization_synth.rpt
report_timing -file timing_synth.rpt
# 保存检查点
write_checkpoint -force counter_synth.dcp
``` ```
**runVivadoSynthesis** #### 3.4.3 命令执行
```typescript
{
projectPath?: string; // 工程路径(可选,如果有工程)
part: string; // 芯片型号
topModule: string; // 顶层模块
files?: string[]; // 源文件(如果没有工程)
constraints?: string; // 约束文件(可选)
mode: 'gui' | 'batch'; // 执行模式
}
```
**runVivadoImplementation** - 启动子进程执行 Vivado 命令
```typescript - 实时捕获标准输出和错误输出
{ - 向前端推送进度信息(解析日志中的进度标记)
dcpFile: string; // 综合后的 .dcp 文件路径
constraints: string; // 约束文件(必需,包含管脚约束)
mode: 'gui' | 'batch'; // 执行模式
}
```
**runVivadoBitstream** #### 3.4.4 结果处理
```typescript
{
dcpFile: string; // 实现后的 .dcp 文件路径
mode: 'gui' | 'batch'; // 执行模式
}
```
### 3.4 后端AI的职责 - 检查执行结果(退出码)
- 解析报告文件,提取关键信息(资源使用、时序)
- 查找产出文件
后端AI负责 #### 3.4.5 文件导入
1. 询问用户必要参数(芯片型号、执行模式等)
2. 理解用户意图,决定调用哪些工具
3. 按正确顺序调用工具(遵循依赖关系)
4. 检查每步执行结果,决定是否继续
5. 汇总结果并展示给用户
#### 3.4.1 询问用户参数 - 根据配置的文件模式查找产出文件
- 复制文件到目标目录
后端必须询问: - 通知用户导入结果
- **芯片型号**(必需):"请提供 FPGA 芯片型号例如xc7a35tcpg236-1"
- **执行模式**(必需):"选择执行模式1) 图形化 2) 后端执行"
- **约束文件**(必需):"请提供约束文件(.xdc包含管脚约束和时序约束"
#### 3.4.2 理解依赖关系
后端AI需要理解
```
创建工程 → 综合 → 实现 → 生成比特流
```
如果用户说"做实现",后端应该:
1. 先调用 `createVivadoProject` 创建工程
2. 再调用 `runVivadoSynthesis` 执行综合
3. 最后调用 `runVivadoImplementation` 执行实现
#### 3.4.3 逐步调用工具
```
步骤1: 调用 createVivadoProject
检查 response.success
如果失败 → 停止并报错
步骤2: 调用 runVivadoSynthesis
检查 response.success
如果失败 → 停止并报错
步骤3: 调用 runVivadoImplementation
检查 response.success
返回最终结果
```
### 3.5 UI 交互 ### 3.5 UI 交互
@ -246,187 +212,90 @@ interface VivadoToolResponse {
#### 3.6.1 工具定义 #### 3.6.1 工具定义
后端注册 4 个独立工具: 后端在工具列表中添加 Vivado 工具:
```json ```json
{ {
"name": "createVivadoProject", "name": "runVivado",
"description": "创建 Vivado 工程。需要先询问用户芯片型号和执行模式。", "description": "调用 Vivado 执行综合、实现或生成比特流。使用前必须先询问用户芯片型号等必要参数。",
"parameters": { "parameters": {
"projectName": "项目名称", "command": "命令类型synthesis/implementation/bitstream",
"part": "芯片型号(必须从用户获取)",
"topModule": "顶层模块名", "topModule": "顶层模块名",
"files": "文件列表", "files": "输入文件列表",
"constraints": "约束文件(可选", "part": "FPGA 芯片型号(必须从用户获取",
"mode": "执行模式gui/batch" "constraints": "约束文件路径(可选"
}
},
{
"name": "runVivadoSynthesis",
"description": "执行 Vivado 综合。前端不检查依赖,后端需确保工程已创建。",
"parameters": {
"projectPath": "工程路径(可选)",
"part": "芯片型号",
"topModule": "顶层模块",
"files": "源文件(如果没有工程)",
"constraints": "约束文件(可选)",
"mode": "执行模式gui/batch"
}
},
{
"name": "runVivadoImplementation",
"description": "执行 Vivado 实现。前端不检查依赖,后端需确保综合已完成且提供约束文件。",
"parameters": {
"dcpFile": "综合后的 .dcp 文件路径",
"constraints": "约束文件(必需,包含管脚约束)",
"mode": "执行模式gui/batch"
}
},
{
"name": "runVivadoBitstream",
"description": "生成比特流。前端不检查依赖,后端需确保实现已完成。",
"parameters": {
"dcpFile": "实现后的 .dcp 文件路径",
"mode": "执行模式gui/batch"
} }
} }
``` ```
#### 3.6.2 后端调用示例 #### 3.6.2 后端交互流程
**场景:用户要求完整流程** **关键点**:后端必须先收集必要参数,再调用工具
1. **用户发起请求**"打开 Vivado" 或 "用 Vivado 综合"
2. **后端识别意图**:需要调用 runVivado 工具
3. **后端询问参数**
- FPGA 芯片型号(必须)
- 约束文件(可选)
- 确认顶层模块名
4. **用户提供参数**
5. **后端调用工具**:传递完整参数给前端
6. **前端执行**VivadoRunner 执行命令
7. **返回结果**:后端接收结果并展示给用户
#### 3.6.3 调用示例(完整交互)
``` ```
用户: 用 Vivado 跑完整流程 用户:帮我用 Vivado 综合一下 counter.v
AI: 请提供芯片型号和执行模式 AI:好的,我将使用 Vivado 进行综合。请提供以下信息:
用户: xc7a35tcpg236-1,后端执行 1. FPGA 芯片型号(例如:xc7a35tcpg236-1
2. 是否有约束文件(.xdc
AI 执行: 用户xc7a35tcpg236-1没有约束文件
1. [调用] createVivadoProject({ projectName: "counter", part: "xc7a35tcpg236-1", ... })
[结果] { success: true, outputFiles: ["counter.xpr"] }
2. [调用] runVivadoSynthesis({ projectPath: "counter.xpr", ... }) AI收到开始综合...
[结果] { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} } [调用工具] runVivado
参数:
- command: synthesis
- topModule: counter
- files: ["counter.v"]
- part: "xc7a35tcpg236-1"
3. [调用] runVivadoImplementation({ dcpFile: "counter_synth.dcp", constraints: "counter.xdc", ... }) [执行中...]
[结果] { success: true, outputFiles: ["counter_impl.dcp"], reports: {...} } Vivado 综合完成!
- 芯片型号xc7a35tcpg236-1
4. [调用] runVivadoBitstream({ dcpFile: "counter_impl.dcp", ... }) - 执行时间45 秒
[结果] { success: true, outputFiles: ["counter.bit"] } - 资源使用LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%)
- 产出文件counter_synth.dcp, utilization_synth.rpt
AI 回复: 完成!已生成 counter.bit 文件 - 已自动导入到vivado_output/
``` ```
## 4. 用户场景 ## 4. 用户场景
### 4.1 场景一:完整流程执行 ### 4.1 场景一:单步综合
**用户输入**"用 Vivado 跑完整个 FPGA 流程" 1. 用户编写完 Verilog 代码
2. 在聊天中输入:"用 Vivado 综合一下 counter.v"
3. AI 调用 `runVivado` 工具
4. 插件执行 Vivado 综合
5. 综合完成后,显示资源使用情况,自动导入报告文件
**AI 交互** ### 4.2 场景二:完整流程
```
AI: 好的,我将使用 Vivado 执行完整的 FPGA 开发流程。请提供以下信息:
1. FPGA 芯片型号例如xc7a35tcpg236-1 1. 用户输入:"用 Vivado 跑完整个流程"
2. 请选择要执行的步骤(可多选) 2. AI 依次调用
- 综合Synthesis - 综合Synthesis
- 实现Implementation - 实现Implementation
- 生成比特流Bitstream - 生成比特流Bitstream
3. 执行模式: 3. 每个步骤完成后显示结果
- 图形化模式:打开 Vivado GUI 4. 最终生成 .bit 文件,用户可以烧录到 FPGA
- 后端执行:自动在后台执行
4. 约束文件路径(.xdc必需
用户: xc7a35tcpg236-1全部步骤后端执行约束文件是 counter.xdc ### 4.3 场景三:查看报告
AI: 收到!执行计划: 1. Vivado 执行完成后
1. 创建工程 2. 用户点击导入的报告文件
2. 综合 3. 在编辑器中查看资源使用、时序分析等信息
3. 实现
4. 生成比特流
开始执行...
[步骤 1/4] 创建工程...
✓ 工程创建成功(耗时 2s
[步骤 2/4] 综合中...
✓ 综合完成(耗时 45s
资源使用LUT 32/20800 (0.15%), FF 8/41600 (0.02%)
[步骤 3/4] 实现中...
✓ 实现完成(耗时 120s
时序WNS 5.234ns, TNS 0.000ns(满足要求)
[步骤 4/4] 生成比特流...
✓ 比特流生成完成(耗时 15s
所有步骤执行完毕!产出文件已导入到 vivado_output/ 目录。
```
### 4.2 场景二:只执行实现(自动补充依赖)
**用户输入**"用 Vivado 做实现"
**AI 交互**
```
AI: 请提供以下信息:
1. FPGA 芯片型号
2. 执行模式(图形化/后端执行)
用户: xc7a35tcpg236-1后端执行
AI: 注意:实现依赖综合,我将自动执行以下步骤:
1. 创建工程
2. 综合(前置依赖)
3. 实现(您选择的步骤)
是否继续?
用户: 是
AI: 开始执行...
[自动执行创建工程 → 综合 → 实现]
```
### 4.3 场景三:图形化模式
**用户输入**"打开 Vivado 图形界面做综合"
**AI 交互**
```
AI: 请提供芯片型号:
用户: xc7a35tcpg236-1
AI: 正在创建工程并打开 Vivado GUI...
✓ 工程已创建
✓ Vivado 图形界面已启动
您现在可以在 Vivado 中手动操作。完成后,产出文件将保存在工程目录中。
```
### 4.4 场景四:执行中断处理
**执行过程**
```
[步骤 1/3] 创建工程...
✓ 成功
[步骤 2/3] 综合中...
✗ 综合失败
错误ERROR: [Synth 8-439] module 'counter' not found
AI: 综合失败,发现以下问题:
- 找不到模块 'counter'
建议检查:
1. 模块名是否正确
2. 文件中是否定义了该模块
3. 是否有语法错误
执行已停止,请修复错误后重试。
```
## 5. 技术约束 ## 5. 技术约束
@ -485,9 +354,6 @@ AI: 综合失败,发现以下问题:
- 用户需要配置正确的 Vivado 路径 - 用户需要配置正确的 Vivado 路径
- 需要设置环境变量 `XILINX_VIVADO` - 需要设置环境变量 `XILINX_VIVADO`
- 需要有效的 Vivado 许可证 - 需要有效的 Vivado 许可证
- **需要提供 .xdc 约束文件**
- **管脚约束**必需定义信号与 FPGA 引脚的映射关系实现阶段必须提供
- **时序约束**强烈推荐定义时钟频率和时序要求确保设计满足性能指标
## 8. 后续扩展 ## 8. 后续扩展

View File

@ -1,166 +1,637 @@
# Vivado 联动前后端对接文档 # Vivado 联动前后端对接文档
## 1. 前端提供的工具 ## 1. 概述
前端提供 4 个独立工具,每个工具执行一个 Vivado 命令 本文档描述后端 AI 服务如何调用前端的 Vivado 工具,以及前端如何响应和返回结果
### 1.1 createVivadoProject - 创建工 ### 1.1 调用流
**参数** ```
```typescript 后端 AI 服务
↓ (1) 发送工具调用请求
前端 Extension (MessageHandler)
↓ (2) 解析请求,调用 VivadoRunner
VivadoRunner
↓ (3) 执行 Vivado实时推送进度
前端 Webview
↓ (4) 显示进度和结果
前端 Extension
↓ (5) 返回执行结果给后端
后端 AI 服务
```
## 2. 工具定义(后端)
### 2.1 工具注册
后端需要在工具列表中注册 `runVivado` 工具:
```json
{ {
projectName: string; // 项目名称 "name": "runVivado",
part: string; // 芯片型号(如 xc7a35tcpg236-1 "description": "调用本地 Vivado 工具执行 FPGA 综合、实现或生成比特流。用于将 Verilog 代码部署到 FPGA 硬件。使用前必须先询问用户必要的参数(如芯片型号、执行模式)。",
topModule: string; // 顶层模块名 "inputSchema": {
files: string[]; // 源文件路径列表 "type": "object",
constraints?: string; // 约束文件路径(可选) "properties": {
mode: 'gui' | 'batch'; // gui=打开图形界面batch=后台执行 "command": {
"type": "string",
"enum": ["synthesis", "implementation", "bitstream"],
"description": "要执行的命令类型synthesis(综合)、implementation(实现)、bitstream(生成比特流)"
},
"topModule": {
"type": "string",
"description": "顶层模块名称"
},
"files": {
"type": "array",
"items": { "type": "string" },
"description": "输入的 Verilog 文件路径列表"
},
"constraints": {
"type": "string",
"description": "约束文件路径(.xdc 文件),可选"
},
"part": {
"type": "string",
"description": "FPGA 芯片型号(如 xc7a35tcpg236-1必须从用户处获取"
},
"mode": {
"type": "string",
"enum": ["batch", "gui"],
"description": "执行模式batch(后台批处理)、gui(打开图形界面),必须询问用户"
}
},
"required": ["command", "topModule", "files", "part", "mode"]
}
} }
``` ```
**返回** ### 2.2 后端调用前的准备工作
```typescript
**重要**:后端在调用 `runVivado` 工具前,必须先向用户询问必要参数:
1. **芯片型号part**:必须询问,例如 "xc7a35tcpg236-1"
2. **执行模式mode**:必须询问用户选择
- `batch`:后台批处理执行,自动完成
- `gui`:打开 Vivado 图形界面,用户手动操作
3. **顶层模块名**:可从文件名推断,但建议确认
4. **约束文件**:询问是否有时序约束文件(.xdc
**询问示例**
```
AI: 我将使用 Vivado 进行综合。请提供以下信息:
1. FPGA 芯片型号例如xc7a35tcpg236-1
2. 执行模式:
- 批处理模式:后台自动执行,完成后返回结果
- 图形界面:打开 Vivado GUI您可以手动操作
3. 是否有约束文件(.xdc
用户: xc7a35tcpg236-1批处理模式没有约束文件
AI: 好的,开始后台综合...
[调用 runVivado 工具]
```
### 2.3 调用示例
#### 示例 1综合单个文件批处理模式
```json
{ {
success: boolean; // 是否成功 "tool": "runVivado",
command: "create_project"; "parameters": {
executionTime: number; // 执行时间(毫秒) "command": "synthesis",
output: string; // 完整日志 "topModule": "counter",
error?: string; // 失败原因(如果失败) "files": ["counter.v"],
outputFiles?: string[]; // 产出的工程文件路径 "part": "xc7a35tcpg236-1",
"mode": "batch"
}
} }
``` ```
### 1.2 runVivadoSynthesis - 综合 #### 示例 2综合带约束文件图形界面模式
**参数** ```json
```typescript
{ {
projectPath?: string; // 工程路径(可选) "tool": "runVivado",
part: string; // 芯片型号 "parameters": {
topModule: string; // 顶层模块 "command": "synthesis",
files?: string[]; // 源文件(如果没有工程) "topModule": "uart_top",
constraints?: string; // 约束文件(可选) "files": ["uart_tx.v", "uart_rx.v", "uart_top.v"],
mode: 'gui' | 'batch'; "constraints": "constraints.xdc",
"part": "xc7k325tffg900-2",
"mode": "gui"
}
} }
``` ```
**返回** #### 示例 3实现批处理模式
```typescript
```json
{ {
success: boolean; "tool": "runVivado",
command: "synthesis"; "parameters": {
executionTime: number; "command": "implementation",
output: string; "topModule": "counter",
error?: string; // 失败原因 "part": "xc7a35tcpg236-1",
outputFiles?: string[]; // .dcp 文件等 "mode": "batch"
reports?: { }
resources?: string; // 资源使用摘要 }
timing?: string; // 时序摘要 ```
#### 示例 4生成比特流图形界面
```json
{
"tool": "runVivado",
"parameters": {
"command": "bitstream",
"topModule": "counter",
"part": "xc7a35tcpg236-1",
"mode": "gui"
}
}
```
## 3. 前端接收和处理
### 3.1 后端如何控制前端
**核心机制**:后端通过调用 `runVivado` 工具来控制前端执行 Vivado 命令。
**控制流程**
1. 后端识别用户意图(如"打开 Vivado"、"开始仿真"
2. 后端向用户询问必要参数(芯片型号等)
3. 后端调用 `runVivado` 工具,传递参数
4. 前端接收工具调用,执行相应操作
5. 前端返回执行结果给后端
6. 后端将结果展示给用户
**示例场景**
```
用户输入:"打开 Vivado 进行综合"
后端处理:
1. 识别意图 → 需要调用 runVivado 工具
2. 检查参数 → 缺少芯片型号
3. 询问用户 → "请提供 FPGA 芯片型号"
4. 用户回复 → "xc7a35tcpg236-1"
5. 调用工具 → runVivado({ command: "synthesis", part: "xc7a35tcpg236-1", ... })
6. 前端执行 → VivadoRunner 启动 Vivado
7. 返回结果 → { success: true, ... }
8. 展示结果 → "综合完成,耗时 45 秒"
```
### 3.2 MessageHandler 处理逻辑
前端在 `messageHandler.ts` 中添加工具处理:
```typescript
// src/utils/messageHandler.ts
export async function handleToolExecution(
panel: vscode.WebviewPanel,
toolName: string,
parameters: any
): Promise<any> {
if (toolName === 'runVivado') {
return await handleVivadoTool(panel, parameters);
}
// 其他工具处理...
}
async function handleVivadoTool(
panel: vscode.WebviewPanel,
parameters: any
): Promise<VivadoToolResponse> {
const { command, topModule, files, constraints, part, mode } = parameters;
// 验证必需参数
if (!part) {
return {
success: false,
command,
executionTime: 0,
output: '',
error: '缺少必需参数芯片型号part。后端应该先询问用户。'
};
}
if (!mode) {
return {
success: false,
command,
executionTime: 0,
output: '',
error: '缺少必需参数执行模式mode。后端应该询问用户选择 batch 或 gui。'
};
}
// 构建请求
const request: VivadoToolRequest = {
command,
parameters: {
topModule,
files,
constraints,
part,
mode
},
importOutput: {
enabled: mode === 'batch', // 只有批处理模式才自动导入
targetDir: path.join(
vscode.workspace.workspaceFolders![0].uri.fsPath,
'vivado_output'
)
}
}; };
// 向前端发送开始消息
panel.webview.postMessage({
type: 'vivado-start',
command
});
// 执行 Vivado
const response = await runVivado(request, (progress) => {
// 实时推送进度到前端
panel.webview.postMessage({
type: 'vivado-progress',
progress
});
});
// 向前端发送完成消息
panel.webview.postMessage({
type: 'vivado-complete',
response
});
// 返回结果给后端
return response;
} }
``` ```
### 1.3 runVivadoImplementation - 实现 ## 4. 响应格式
**参数** ### 4.1 成功响应
```typescript
```json
{ {
dcpFile: string; // 综合后的 .dcp 文件路径 "success": true,
mode: 'gui' | 'batch'; "command": "synthesis",
"executionTime": 45230,
"output": "Vivado 执行日志...",
"importedFiles": [
"/path/to/vivado_output/counter_synth.dcp",
"/path/to/vivado_output/counter_utilization_synth.rpt"
],
"reports": {
"resources": "LUT: 32/20800 (0.15%)\nFF: 8/41600 (0.02%)",
"timing": "WNS: 5.234ns, TNS: 0.000ns"
}
} }
``` ```
**返回** ### 4.2 失败响应
```typescript
```json
{ {
success: boolean; "success": false,
command: "implementation"; "command": "synthesis",
executionTime: number; "executionTime": 1250,
output: string; "output": "部分执行日志...",
error?: string; // 失败原因 "error": "ERROR: [Synth 8-439] module 'counter' not found"
outputFiles?: string[]; // 实现后的 .dcp 文件等
reports?: {
resources?: string;
timing?: string;
};
} }
``` ```
### 1.4 runVivadoBitstream - 生成比特流 ## 5. 后端使用指南
**参数** ### 5.1 AI 对话流程(完整版)
```typescript
```
用户:帮我用 Vivado 综合一下 counter.v
AI 分析:
1. 用户想要综合 Verilog 文件
2. 需要调用 runVivado 工具
3. 命令类型是 synthesis
4. 顶层模块名从文件名推断为 counter
5. 输入文件是 counter.v
6. ⚠️ 缺少必要参数:芯片型号
AI 回复用户:
"好的,我将使用 Vivado 进行综合。请提供以下信息:
1. FPGA 芯片型号例如xc7a35tcpg236-1、xc7k325tffg900-2
2. 是否有约束文件(.xdc"
用户xc7a35tcpg236-1没有约束文件
AI 调用工具:
{ {
dcpFile: string; // 实现后的 .dcp 文件路径 "tool": "runVivado",
mode: 'gui' | 'batch'; "parameters": {
"command": "synthesis",
"topModule": "counter",
"files": ["counter.v"],
"part": "xc7a35tcpg236-1"
}
} }
前端执行并返回结果
AI 回复用户:
"Vivado 综合完成!
- 执行时间45.2 秒
- 芯片型号xc7a35tcpg236-1
- 资源使用LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%)
- 产出文件已导入到 vivado_output 目录"
``` ```
**返回** ### 5.2 完整流程示例
```typescript
```
用户:用 Vivado 跑完整个流程
AI好的我将依次执行综合、实现和生成比特流。请提供
1. FPGA 芯片型号
2. 顶层模块名
3. 是否有约束文件
用户xc7a35tcpg236-1顶层模块是 counter没有约束文件
AI收到开始执行...
步骤 1综合
[调用] runVivado { command: "synthesis", topModule: "counter", files: ["counter.v"], part: "xc7a35tcpg236-1" }
[结果] 综合成功,耗时 45s
步骤 2实现
[调用] runVivado { command: "implementation", topModule: "counter", part: "xc7a35tcpg236-1" }
[结果] 实现成功,耗时 120s时序满足要求
步骤 3生成比特流
[调用] runVivado { command: "bitstream", topModule: "counter", part: "xc7a35tcpg236-1" }
[结果] 比特流生成成功文件counter.bit
完成!所有文件已导入到 vivado_output 目录。
```
## 6. 错误处理
### 6.1 常见错误
#### 错误 1Vivado 未配置
```json
{ {
success: boolean; "success": false,
command: "bitstream"; "error": "Vivado 未配置,请在设置中配置 Vivado 路径"
executionTime: number;
output: string;
error?: string; // 失败原因
outputFiles?: string[]; // .bit 文件路径
} }
``` ```
## 2. 前端职责 **AI 应该回复**
"Vivado 尚未配置,请先在插件设置中配置 Vivado 的安装路径。"
- 接收后端工具调用 #### 错误 2文件不存在
- 生成对应的 TCL 脚本
- 执行 Vivado 命令
- 捕获输出日志
- 解析报告文件(提取资源和时序摘要)
- 返回执行结果
**前端不做** ```json
- 不检查依赖关系
- 不验证执行顺序
- 不控制流程
## 3. 后端职责
- 询问用户参数(芯片型号、执行模式等)
- 理解依赖关系(创建工程 → 综合 → 实现 → 生成比特流)
- 按正确顺序调用工具
- 检查每步的 `success` 字段
- 如果失败,读取 `error` 字段并提示用户
- 汇总结果展示给用户
## 4. 调用示例
```
用户: 用 Vivado 做综合
后端:
1. 询问芯片型号 → xc7a35tcpg236-1
2. 询问执行模式 → batch
3. 调用 createVivadoProject(...)
返回: { success: true, outputFiles: ["counter.xpr"] }
4. 调用 runVivadoSynthesis(...)
返回: { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} }
5. 展示结果给用户
```
## 5. 错误处理
如果某步失败:
```typescript
{ {
success: false, "success": false,
error: "ERROR: [Synth 8-439] module 'counter' not found", "error": "输入文件不存在: counter.v"
output: "详细日志..."
} }
``` ```
后端应该 **AI 应该回复**
1. 停止后续步骤 "找不到文件 counter.v请确认文件路径是否正确。"
2. 提取 `error` 字段
3. 给用户提示和建议 #### 错误 3综合失败
```json
{
"success": false,
"error": "ERROR: [Synth 8-439] module 'counter' not found",
"output": "详细日志..."
}
```
**AI 应该回复**
"综合失败,错误信息:找不到模块 'counter'。请检查:
1. 模块名是否正确
2. 文件中是否定义了该模块
3. 是否有语法错误"
### 6.2 错误处理建议
后端收到 `success: false` 时:
1. 提取 `error` 字段中的错误信息
2. 分析错误类型(配置问题、文件问题、语法问题等)
3. 给用户提供具体的解决建议
4. 必要时可以查看 `output` 字段获取详细日志
## 7. 进度推送(可选)
前端会实时推送进度信息到 Webview后端无需处理但可以了解进度格式
```json
{
"type": "vivado-progress",
"progress": {
"stage": "synthesis",
"percentage": 45,
"message": "正在综合模块 counter..."
}
}
```
## 8. 测试建议
### 8.1 后端测试用例
```javascript
// 测试用例 1基本综合
test('综合单个文件', async () => {
const result = await callTool('runVivado', {
command: 'synthesis',
topModule: 'counter',
files: ['counter.v']
});
expect(result.success).toBe(true);
expect(result.importedFiles.length).toBeGreaterThan(0);
});
// 测试用例 2错误处理
test('文件不存在', async () => {
const result = await callTool('runVivado', {
command: 'synthesis',
topModule: 'test',
files: ['not_exist.v']
});
expect(result.success).toBe(false);
expect(result.error).toContain('不存在');
});
```
## 9. 注意事项
### 9.1 后端必须询问的参数
**关键要求**:后端在调用 `runVivado` 前,必须先向用户收集以下参数:
| 参数 | 是否必须 | 询问方式 | 示例 |
|------|---------|---------|------|
| part芯片型号 | ✅ 必须 | 直接询问 | "请提供 FPGA 芯片型号(如 xc7a35tcpg236-1" |
| mode执行模式 | ✅ 必须 | 提供选项 | "选择执行模式1.批处理 2.图形界面" |
| topModule | ✅ 必须 | 可推断或询问 | 从文件名推断,或询问"顶层模块名是什么?" |
| files | ✅ 必须(综合时) | 可推断或询问 | 从当前文件或工作区推断 |
| constraints | ❌ 可选 | 询问 | "是否有约束文件(.xdc" |
### 9.2 常见用户输入场景
#### 场景 1用户说"打开 Vivado"
```
用户:打开 Vivado
后端分析:用户想使用 Vivado但没有指定具体操作
后端回复:
"好的,请问您想执行什么操作?
1. 综合Synthesis- 将 Verilog 转换为门级网表
2. 实现Implementation- 布局布线
3. 生成比特流Bitstream- 生成 .bit 文件
另外,请提供:
- FPGA 芯片型号例如xc7a35tcpg236-1
- 执行模式:批处理(后台自动)或 图形界面(手动操作)"
```
#### 场景 2用户说"开始仿真"
```
用户:开始仿真
后端分析:用户可能混淆了仿真和综合
后端回复:
"您是想:
1. 使用 iverilog 进行功能仿真(验证逻辑)
2. 使用 Vivado 进行综合(部署到 FPGA
请选择您需要的操作。"
```
#### 场景 3用户提供完整信息
```
用户:用 Vivado 综合 counter.v芯片是 xc7a35tcpg236-1批处理模式
后端分析:信息完整,可以直接调用
后端操作:
[调用] runVivado({
command: "synthesis",
topModule: "counter",
files: ["counter.v"],
part: "xc7a35tcpg236-1",
mode: "batch"
})
```
#### 场景 4用户选择图形界面
```
用户:打开 Vivado 图形界面做综合
后端分析:用户明确要求 GUI 模式
后端询问:
"好的,请提供:
1. FPGA 芯片型号
2. 顶层模块名"
用户xc7a35tcpg236-1, counter
后端操作:
[调用] runVivado({
command: "synthesis",
topModule: "counter",
files: ["counter.v"],
part: "xc7a35tcpg236-1",
mode: "gui"
})
前端执行:
- 生成 TCL 脚本和项目文件
- 执行: vivado counter_project.xpr (打开图形界面)
- 返回: { success: true, message: "Vivado GUI 已启动" }
后端回复:
"Vivado 图形界面已打开,您可以在界面中手动操作。"
```
### 9.3 执行时间
- 综合:小型设计 30s-2min大型设计 5-30min
- 实现:通常是综合时间的 2-3 倍
- 生成比特流:通常 10-30s
后端应该设置合理的超时时间(建议 10 分钟)。
### 9.4 依赖关系
- `implementation` 需要先执行 `synthesis`
- `bitstream` 需要先执行 `implementation`
后端 AI 应该理解这个依赖关系,按顺序调用。
### 9.5 文件路径
- 所有文件路径都是相对于工作区根目录
- 前端会自动解析为绝对路径
- 支持相对路径和绝对路径
## 10. 参数传递详细说明
### 10.1 必需参数
| 参数 | 类型 | 说明 | 获取方式 |
|------|------|------|----------|
| command | string | 命令类型 | 从用户意图推断 |
| topModule | string | 顶层模块名 | 从文件名推断或询问用户 |
| files | string[] | 源文件列表 | 从工作区查找或用户指定 |
| part | string | 芯片型号 | **必须询问用户** |
### 10.2 可选参数
| 参数 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| constraints | string | 约束文件路径 | 无 |
### 10.3 参数验证规则
后端在调用前应验证:
- `part` 格式正确(如 xc7a35tcpg236-1
- `files` 数组不为空
- `topModule` 不为空
- `command` 在枚举值内
## 11. 快速集成清单
后端开发者需要做的事情:
- [ ] 在工具列表中注册 `runVivado` 工具
- [ ] **实现参数询问逻辑(芯片型号等)**
- [ ] 实现工具调用逻辑(发送请求到前端)
- [ ] 处理返回结果success/error
- [ ] 实现错误处理和用户提示
- [ ] 理解三个命令的依赖关系
- [ ] 设置合理的超时时间(建议 10 分钟)
- [ ] 编写测试用例
前端开发者需要做的事情:
- [ ] 实现 `handleVivadoTool` 函数
- [ ] 集成 VivadoRunner
- [ ] 实现进度推送
- [ ] 实现结果展示
- [ ] 处理各种错误情况
- [ ] 验证传入的参数完整性

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,247 @@
# 个人规则功能 - 后端对接文档
## 1. 功能概述
个人规则功能允许用户创建多条自定义规则,这些规则会在每次对话时自动传递给后端,由后端注入到 AI 的系统提示词中,从而影响 AI 的回答风格和行为。
## 2. 前端实现说明
### 2.1 用户界面
- 用户可以在设置页面创建、修改、删除多条规则
- 每条规则包含:规则名称 + 规则内容
- 全局开关:启用/禁用所有规则
### 2.2 规则存储
- 存储位置:`C:\Users\{用户名}\.iccoder\rules\`
- 文件格式:每条规则一个独立的 `.md` 文件
- 文件命名:`rule-{时间戳}.md`
- 文件内容格式:
```markdown
# 规则名称
规则内容详细描述...
```
### 2.3 规则传输逻辑
- **开关开启**:所有规则内容合并后通过 `personalRules` 字段传给后端
- **开关关闭**`personalRules` 字段为 `undefined`,不传给后端
## 3. 后端接口变更
### 3.1 DialogRequest 接口新增字段
在现有的 `DialogRequest` 接口中新增 `personalRules` 字段:
```typescript
export interface DialogRequest {
taskId: string;
message: string;
userId: string;
mode: RunMode;
serviceTier?: ServiceTier;
token?: string;
compactedData?: CompactedMemory;
newMessages?: CompactedMessage[];
knowledgeData?: string;
personalRules?: string; // 新增:个人规则内容
}
```
### 3.2 字段说明
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `personalRules` | `string` | 否 | 用户的个人规则内容,多条规则用 `\n\n` 分隔 |
### 3.3 字段示例
**单条规则:**
```json
{
"message": "帮我写一个排序函数",
"personalRules": "始终使用中文回复,代码注释要详细"
}
```
**多条规则(合并后):**
```json
{
"message": "帮我写一个排序函数",
"personalRules": "始终使用中文回复,代码注释要详细\n\n使用 TypeScript 严格模式\n\n遵循项目编码规范"
}
```
**规则关闭:**
```json
{
"message": "帮我写一个排序函数",
"personalRules": undefined
}
```
## 4. 后端处理要求
### 4.1 接收处理
```typescript
// 伪代码示例
function handleDialogRequest(request: DialogRequest) {
const { message, personalRules, ...otherFields } = request;
// 检查是否有个人规则
if (personalRules && personalRules.trim()) {
// 有规则:注入到系统提示词
return processWithRules(message, personalRules, otherFields);
} else {
// 无规则:正常处理
return processNormal(message, otherFields);
}
}
```
### 4.2 规则注入策略
**重要:规则必须注入到系统提示词层,而不是用户消息层**
推荐的注入顺序(优先级从高到低):
1. **平台安全策略**(最高优先级,不可被覆盖)
2. **产品默认系统提示**
3. **用户个人规则** ← 在这里注入
4. **用户输入消息**
### 4.3 注入示例
```typescript
// 伪代码示例
function buildSystemPrompt(personalRules?: string): string {
let systemPrompt = `
你是一个专业的 AI 助手。
遵循以下基本原则:
- 安全第一
- 准确回答
- 友好交流
`;
// 如果有个人规则,追加到系统提示词
if (personalRules && personalRules.trim()) {
systemPrompt += `\n\n用户的个人偏好和规则\n${personalRules}`;
}
return systemPrompt;
}
function processWithRules(
userMessage: string,
personalRules: string,
otherFields: any
) {
const systemPrompt = buildSystemPrompt(personalRules);
// 调用 AI 模型
return callAIModel({
system: systemPrompt,
user: userMessage,
...otherFields
});
}
```
## 5. 注意事项
### 5.1 安全性
- ⚠️ **个人规则不能覆盖平台安全策略**
- ⚠️ **需要对规则内容进行基本的安全检查**
- ⚠️ **防止注入攻击(如提示词注入)**
### 5.2 长度限制
- 前端已限制单条规则内容,但多条规则合并后可能较长
- 建议后端设置总长度上限(如 10000 字符)
- 超限时可以截断或返回错误提示
### 5.3 兼容性
- `personalRules` 字段为可选字段
- 旧版本前端不传此字段时,后端应正常处理(向后兼容)
- 字段为 `undefined` 或空字符串时,视为无规则
### 5.4 日志记录
建议在日志中记录:
- 本次请求是否包含个人规则
- 规则内容的长度(不要记录完整内容,避免隐私泄露)
- 规则注入是否成功
示例日志:
```
[INFO] Dialog request received
- taskId: abc123
- userId: user456
- hasPersonalRules: true
- rulesLength: 156
- rulesInjected: success
```
## 6. 测试建议
### 6.1 功能测试
1. **无规则场景**`personalRules` 为 `undefined`,正常对话
2. **单条规则**:传入一条规则,验证 AI 是否遵循
3. **多条规则**:传入多条规则,验证 AI 是否同时遵循
4. **规则冲突**:传入相互矛盾的规则,观察 AI 行为
5. **超长规则**:传入超长内容,验证截断或错误处理
### 6.2 安全测试
1. **提示词注入**:尝试在规则中注入恶意提示词
2. **覆盖安全策略**:尝试用规则覆盖平台安全限制
3. **特殊字符**:测试规则中包含特殊字符的情况
### 6.3 性能测试
1. **大量规则**:测试 10+ 条规则的性能影响
2. **高频请求**:测试规则注入对响应时间的影响
## 7. 错误处理
### 7.1 可能的错误场景
| 错误场景 | 处理方式 |
|---------|---------|
| 规则内容为空字符串 | 视为无规则,正常处理 |
| 规则内容超长 | 截断或返回错误 |
| 规则包含非法内容 | 过滤或拒绝请求 |
| 规则注入失败 | 降级为无规则对话 |
### 7.2 错误响应示例
```json
{
"error": {
"code": "RULES_TOO_LONG",
"message": "个人规则内容超过长度限制(最大 10000 字符)"
}
}
```
## 8. 验收标准
### 8.1 基本功能
- [ ] 能正确接收 `personalRules` 字段
- [ ] 规则能正确注入到系统提示词
- [ ] 规则关闭时不影响正常对话
- [ ] 多条规则能同时生效
### 8.2 安全性
- [ ] 规则不能覆盖平台安全策略
- [ ] 有基本的内容安全检查
- [ ] 日志中不记录完整规则内容
### 8.3 兼容性
- [ ] 旧版本前端(无此字段)能正常工作
- [ ] 字段为 `undefined` 时正常处理
## 9. 联系方式
如有疑问,请联系前端开发团队。
---
**文档版本**v1.0
**最后更新**2026-03-07

View File

@ -0,0 +1,379 @@
# Webpack 打包优化完整教程
## 目录
1. [优化前的问题](#优化前的问题)
2. [优化方案详解](#优化方案详解)
3. [配置对比](#配置对比)
4. [使用指南](#使用指南)
5. [效果验证](#效果验证)
---
## 优化前的问题
### 原始配置存在的问题
```javascript
// ❌ 问题1固定使用 none 模式
mode: 'none'
// 导致:生产环境代码不压缩,体积大
// ❌ 问题2没有 Tree Shaking
// 导致:未使用的代码也被打包
// ❌ 问题3ts-loader 默认配置
loader: 'ts-loader'
// 导致:每次编译都做类型检查,速度慢
// ❌ 问题4没有性能监控
// 导致:打包体积过大时不知道
```
---
## 优化方案详解
### 1. 自动模式切换
**原理**:根据环境变量自动选择打包模式
```javascript
// 优化前
mode: 'none'
// 优化后
mode: process.env.NODE_ENV === 'production' ? 'production' : 'none'
```
**效果**
- 开发模式:代码可读,方便调试
- 生产模式:自动压缩,体积减小 40-60%
---
### 2. Tree Shaking摇树优化
**原理**:移除未使用的代码
```javascript
optimization: {
minimize: process.env.NODE_ENV === 'production',
usedExports: true // 标记未使用的导出
}
```
**示例**
```javascript
// utils.ts
export function usedFunc() { }
export function unusedFunc() { } // 不会被打包
// main.ts
import { usedFunc } from './utils';
```
**效果**:减少 10-30% 体积
---
### 3. 加快编译速度
**原理**:跳过类型检查,只做转译
```javascript
{
loader: 'ts-loader',
options: {
transpileOnly: true, // 跳过类型检查
compilerOptions: {
sourceMap: true
}
}
}
```
**说明**
- 类型检查交给 IDE 和 CI
- 编译速度提升 50-70%
---
### 4. 自动清理旧文件
```javascript
output: {
clean: true // 每次打包前清空 dist 目录
}
```
**效果**:避免旧文件残留
---
### 5. 性能监控
```javascript
performance: {
hints: 'warning',
maxAssetSize: 2 * 1024 * 1024, // 2MB
maxEntrypointSize: 2 * 1024 * 1024
}
```
**效果**:超过 2MB 会警告
---
### 6. Source Map 优化
```javascript
devtool: process.env.NODE_ENV === 'production'
? 'hidden-source-map' // 生产:隐藏源码
: 'nosources-source-map' // 开发:保留调试信息
```
---
### 7. 模块解析优化
```javascript
resolve: {
extensions: ['.ts', '.js'],
mainFields: ['module', 'main'] // 优先使用 ES 模块
}
```
**效果**:更好的 Tree Shaking 效果
---
## 配置对比
### 优化前
```javascript
const extensionConfig = {
target: 'node',
mode: 'none', // 固定模式
entry: './src/extension.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2'
// 没有 clean
},
module: {
rules: [{
test: /\.ts$/,
use: [{ loader: 'ts-loader' }] // 默认配置
}]
},
devtool: 'nosources-source-map' // 固定
// 没有 optimization
// 没有 performance
};
```
### 优化后
```javascript
const extensionConfig = {
target: 'node',
mode: process.env.NODE_ENV === 'production' ? 'production' : 'none',
entry: './src/extension.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
clean: true // ✅ 自动清理
},
resolve: {
extensions: ['.ts', '.js'],
mainFields: ['module', 'main'] // ✅ 优化解析
},
module: {
rules: [{
test: /\.ts$/,
use: [{
loader: 'ts-loader',
options: {
transpileOnly: true, // ✅ 加速编译
compilerOptions: { sourceMap: true }
}
}]
}]
},
devtool: process.env.NODE_ENV === 'production'
? 'hidden-source-map'
: 'nosources-source-map',
optimization: {
minimize: process.env.NODE_ENV === 'production',
usedExports: true // ✅ Tree Shaking
},
performance: {
hints: 'warning',
maxAssetSize: 2 * 1024 * 1024,
maxEntrypointSize: 2 * 1024 * 1024
}
};
```
---
## 使用指南
### 开发模式
```bash
# 单次编译
pnpm run compile
# 监听模式(推荐)
pnpm run watch
```
**特点**
- 不压缩代码
- 快速编译
- 保留调试信息
---
### 生产模式
#### Windows
```bash
set NODE_ENV=production && pnpm run package
```
#### macOS/Linux
```bash
NODE_ENV=production pnpm run package
```
**特点**
- 代码压缩
- Tree Shaking
- 隐藏源码
---
### 一键打包 VSIX
```bash
# Windows
set NODE_ENV=production && pnpm run package && npx vsce package
# macOS/Linux
NODE_ENV=production pnpm run package && npx vsce package
```
---
## 效果验证
### 1. 查看打包体积
```bash
# Windows
dir dist\extension.js
# macOS/Linux
ls -lh dist/extension.js
```
### 2. 对比测试
| 模式 | 体积 | 编译时间 | 可读性 |
|------|------|----------|--------|
| 开发模式 | ~800KB | 5s | 高 |
| 生产模式 | ~400KB | 8s | 低(压缩) |
### 3. 性能警告
如果看到这个警告:
```
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (2 MiB).
```
**解决方案**
1. 检查是否引入了不必要的依赖
2. 将大型库添加到 `externals`
3. 考虑代码分割
---
## 常见问题
### Q1: 为什么开发模式不压缩?
**A**: 保持代码可读性,方便调试和查看错误堆栈。
### Q2: transpileOnly 会影响类型安全吗?
**A**: 不会。IDE 和 `tsc --noEmit` 仍会做类型检查。
### Q3: 如何查看 Tree Shaking 效果?
**A**: 使用 `webpack-bundle-analyzer`
```bash
pnpm add -D webpack-bundle-analyzer
```
### Q4: 生产模式编译失败怎么办?
**A**: 先用开发模式确认代码无误,再切换生产模式。
---
## 进阶优化(可选)
### 1. 排除更多依赖
```javascript
externals: {
vscode: 'commonjs vscode',
'node-notifier': 'commonjs node-notifier',
// 如果这些库很大,可以排除
'vcdrom': 'commonjs vcdrom',
'@wavedrom/doppler': 'commonjs @wavedrom/doppler'
}
```
### 2. 代码分割
```javascript
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
}
}
```
### 3. 缓存优化
```javascript
{
loader: 'ts-loader',
options: {
transpileOnly: true,
experimentalWatchApi: true // 监听模式优化
}
}
```
---
## 总结
通过这些优化:
- ✅ 生产体积减少 40-60%
- ✅ 编译速度提升 50-70%
- ✅ 自动清理和监控
- ✅ 更好的开发体验
**推荐工作流**
1. 开发时用 `pnpm run watch`
2. 提交前用 `pnpm run compile` 检查
3. 发布前用生产模式打包

View File

@ -117,6 +117,11 @@
"configuration": { "configuration": {
"title": "IC Coder", "title": "IC Coder",
"properties": { "properties": {
"ic-coder.personalRulesEnabled": {
"type": "boolean",
"default": true,
"description": "启用个人规则"
},
"ic-coder.enableSystemNotification": { "ic-coder.enableSystemNotification": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,

View File

@ -205,3 +205,8 @@ export const successIconSvg = `<svg t="1771828214449" class="icon" viewBox="0 0
* 任务完成的图标svg * 任务完成的图标svg
*/ */
export const taskCompleteIconSvg = `<svg t="1773302386044" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4798" width="16" height="16"><path d="M512 42.666667C253.866667 42.666667 42.666667 253.866667 42.666667 512s211.2 469.333333 469.333333 469.333333 469.333333-211.2 469.333333-469.333333S770.133333 42.666667 512 42.666667z m221.866667 377.6L488.533333 663.466667c-8.533333 8.533333-19.2 12.8-29.866666 12.8s-21.333333-4.266667-29.866667-12.8l-138.666667-138.666667c-17.066667-17.066667-17.066667-42.666667 0-59.733333 17.066667-17.066667 42.666667-17.066667 59.733334 0l108.8 108.8 215.466666-215.466667c17.066667-17.066667 42.666667-17.066667 59.733334 0 17.066667 17.066667 17.066667 44.8 0 61.866667z" fill="#1afa29" p-id="4799" data-spm-anchor-id="a313x.search_index.0.i0.123d3a812ZEn1Z" class=""></path></svg>`; export const taskCompleteIconSvg = `<svg t="1773302386044" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4798" width="16" height="16"><path d="M512 42.666667C253.866667 42.666667 42.666667 253.866667 42.666667 512s211.2 469.333333 469.333333 469.333333 469.333333-211.2 469.333333-469.333333S770.133333 42.666667 512 42.666667z m221.866667 377.6L488.533333 663.466667c-8.533333 8.533333-19.2 12.8-29.866666 12.8s-21.333333-4.266667-29.866667-12.8l-138.666667-138.666667c-17.066667-17.066667-17.066667-42.666667 0-59.733333 17.066667-17.066667 42.666667-17.066667 59.733334 0l108.8 108.8 215.466666-215.466667c17.066667-17.066667 42.666667-17.066667 59.733334 0 17.066667 17.066667 17.066667 44.8 0 61.866667z" fill="#1afa29" p-id="4799" data-spm-anchor-id="a313x.search_index.0.i0.123d3a812ZEn1Z" class=""></path></svg>`;
/**
* 个人规则的图标svg
*/
export const peopleRules = `<svg t="1772851533961" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11188" width="16" height="16"><path d="M652.8 534.4c70.4-44.8 115.2-124.8 115.2-214.4 0-140.8-115.2-256-256-256s-256 115.2-256 256c0 89.6 44.8 169.6 115.2 214.4C192 592 64 761.6 64 960h64c0-211.2 172.8-384 384-384s384 172.8 384 384h64c0-198.4-128-368-307.2-425.6zM512 512c-105.6 0-192-86.4-192-192s86.4-192 192-192 192 86.4 192 192-86.4 192-192 192z" fill="#6caed4" p-id="11189"></path></svg>`;

View File

@ -22,6 +22,12 @@ import {
handleOpenFileDiff, handleOpenFileDiff,
startChangeSession, startChangeSession,
} from "../../utils/messageHandler"; } from "../../utils/messageHandler";
import {
loadPersonalRules,
savePersonalRule,
updatePersonalRule,
deletePersonalRule,
} from "../../utils/personalRulesManager";
import { compactDialog } from "../../services/apiClient"; import { compactDialog } from "../../services/apiClient";
import { ChatHistoryManager } from "../../utils/chatHistoryManager"; import { ChatHistoryManager } from "../../utils/chatHistoryManager";
import { getCachedUserInfo } from "../../services/userService"; import { getCachedUserInfo } from "../../services/userService";
@ -392,5 +398,63 @@ export async function handleWebviewMessage(
vscode.env.openExternal(vscode.Uri.parse(message.url)); vscode.env.openExternal(vscode.Uri.parse(message.url));
} }
break; break;
case "loadPersonalRules":
{
const data = loadPersonalRules();
panel.webview.postMessage({
command: "personalRulesLoaded",
data: data,
});
}
break;
case "savePersonalRule":
{
const success = await savePersonalRule(
message.name,
message.content,
message.enabled,
);
if (success) {
const data = loadPersonalRules();
panel.webview.postMessage({
command: "personalRulesLoaded",
data: data,
});
}
}
break;
case "updatePersonalRule":
{
const success = await updatePersonalRule(
message.filename,
message.name,
message.content,
message.enabled,
);
if (success) {
const data = loadPersonalRules();
panel.webview.postMessage({
command: "personalRulesLoaded",
data: data,
});
}
}
break;
case "deletePersonalRule":
{
const success = await deletePersonalRule(message.filename);
if (success) {
const data = loadPersonalRules();
panel.webview.postMessage({
command: "personalRulesLoaded",
data: data,
});
}
}
break;
} }
} }

View File

@ -28,6 +28,7 @@ import type {
PlanConfirmEvent, PlanConfirmEvent,
} from "../types/api"; } from "../types/api";
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient"; import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
import { getActiveRules } from "../utils/personalRulesManager";
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"; import { updateCachedBalance } from "./creditsService";
@ -502,6 +503,7 @@ export class DialogSession {
compactedData: compactedData || undefined, compactedData: compactedData || undefined,
newMessages: newMessages.length > 0 ? newMessages : undefined, newMessages: newMessages.length > 0 ? newMessages : undefined,
knowledgeData: knowledgeData || undefined, knowledgeData: knowledgeData || undefined,
personalRules: getActiveRules() || undefined,
}; };
// 追踪用户消息 // 追踪用户消息

View File

@ -48,6 +48,8 @@ export interface DialogRequest {
newMessages?: CompactedMessage[]; newMessages?: CompactedMessage[];
/** 知识图谱数据JSON 字符串,用于恢复知识图谱) */ /** 知识图谱数据JSON 字符串,用于恢复知识图谱) */
knowledgeData?: string; knowledgeData?: string;
/** 个人规则 */
personalRules?: string;
} }
// ============== SSE 事件类型 ============== // ============== SSE 事件类型 ==============

View File

@ -14,7 +14,6 @@ import {
checkVerilogProject, checkVerilogProject,
checkIverilogAvailable, checkIverilogAvailable,
} from "./iverilogRunner"; } from "./iverilogRunner";
import { createVivadoProject, runVivadoSynthesis } from "./vivadoRunner";
import { ChatHistoryManager } from "./chatHistoryManager"; import { ChatHistoryManager } from "./chatHistoryManager";
import { dialogManager, DialogSession } from "../services/dialogService"; import { dialogManager, DialogSession } from "../services/dialogService";
import { userInteractionManager } from "../services/userInteraction"; import { userInteractionManager } from "../services/userInteraction";
@ -308,8 +307,8 @@ async function handleUserMessageWithBackend(
onSegmentUpdate: (segments) => { onSegmentUpdate: (segments) => {
// 过滤掉包含 [调用工具:xxx] 的段落 // 过滤掉包含 [调用工具:xxx] 的段落
const filteredSegments = segments.filter(seg => { const filteredSegments = segments.filter((seg) => {
if (seg.type === 'text' && typeof seg.content === 'string') { if (seg.type === "text" && typeof seg.content === "string") {
return !/\[调用工具:.+?\]/.test(seg.content); return !/\[调用工具:.+?\]/.test(seg.content);
} }
return true; return true;
@ -428,7 +427,7 @@ async function handleUserMessageWithBackend(
}); });
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `错误: ${message}`, text: `服务繁忙,请稍后重试`,
}); });
// 恢复输入状态 // 恢复输入状态
panel.webview.postMessage({ panel.webview.postMessage({
@ -849,10 +848,10 @@ async function handleFileOperation(
const errorMsg = error instanceof Error ? error.message : "操作失败"; const errorMsg = error instanceof Error ? error.message : "操作失败";
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `${errorMsg}`, text: `服务繁忙,请稍后重试`,
}); });
vscode.window.showErrorMessage(errorMsg); vscode.window.showErrorMessage(errorMsg);
await historyManager.addAiMessage(`${errorMsg}`); await historyManager.addAiMessage(`服务繁忙,请稍后重试`);
} }
} }
@ -1469,38 +1468,3 @@ export async function handleOpenFileDiff(
vscode.window.showErrorMessage(`打开 diff 失败: ${error}`); vscode.window.showErrorMessage(`打开 diff 失败: ${error}`);
} }
} }
/**
* 处理 Vivado 工具调用
*/
export async function handleVivadoToolCall(
toolName: string,
params: any
): Promise<any> {
try {
switch (toolName) {
case 'createVivadoProject':
return await createVivadoProject(params);
case 'runVivadoSynthesis':
return await runVivadoSynthesis(params);
default:
return {
success: false,
command: toolName,
executionTime: 0,
output: '',
error: `未知的工具: ${toolName}`
};
}
} catch (error: any) {
return {
success: false,
command: toolName,
executionTime: 0,
output: '',
error: error.message || String(error)
};
}
}

View File

@ -0,0 +1,147 @@
/**
* 个人规则管理工具
* 功能:读写个人规则文件
* 依赖vscode, fs, path
* 使用场景:保存和加载用户的个人规则
*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
/**
* 获取规则目录路径
*/
function getRulesDir(): string {
return path.join(os.homedir(), '.iccoder', 'rules');
}
/**
* 确保规则目录存在
*/
function ensureRulesDir(): void {
const dir = getRulesDir();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
/**
* 从文件内容中提取规则名称
*/
function extractRuleName(content: string): string {
const lines = content.split('\n');
const firstLine = lines[0]?.trim();
if (firstLine && firstLine.startsWith('# ')) {
return firstLine.substring(2).trim();
}
return content.substring(0, 30) + (content.length > 30 ? '...' : '');
}
/**
* 保存新规则
*/
export async function savePersonalRule(name: string, content: string, enabled: boolean): Promise<boolean> {
try {
ensureRulesDir();
const timestamp = Date.now();
const filename = `rule-${timestamp}.md`;
const filePath = path.join(getRulesDir(), filename);
const fileContent = `# ${name}\n\n${content}`;
fs.writeFileSync(filePath, fileContent, 'utf-8');
await vscode.workspace.getConfiguration('ic-coder').update('personalRulesEnabled', enabled, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage('规则已保存');
return true;
} catch (error) {
vscode.window.showErrorMessage(`保存规则失败: ${error}`);
return false;
}
}
/**
* 更新规则
*/
export async function updatePersonalRule(filename: string, name: string, content: string, enabled: boolean): Promise<boolean> {
try {
const filePath = path.join(getRulesDir(), filename);
const fileContent = `# ${name}\n\n${content}`;
fs.writeFileSync(filePath, fileContent, 'utf-8');
await vscode.workspace.getConfiguration('ic-coder').update('personalRulesEnabled', enabled, vscode.ConfigurationTarget.Global);
vscode.window.showInformationMessage('规则已更新');
return true;
} catch (error) {
vscode.window.showErrorMessage(`更新规则失败: ${error}`);
return false;
}
}
/**
* 删除规则
*/
export async function deletePersonalRule(filename: string): Promise<boolean> {
try {
const filePath = path.join(getRulesDir(), filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
vscode.window.showInformationMessage('规则已删除');
return true;
}
return false;
} catch (error) {
vscode.window.showErrorMessage(`删除规则失败: ${error}`);
return false;
}
}
/**
* 加载所有规则
*/
export function loadPersonalRules(): { rules: Array<{ filename: string; name: string; content: string }>; enabled: boolean } {
const enabled = vscode.workspace.getConfiguration('ic-coder').get<boolean>('personalRulesEnabled', true);
const dir = getRulesDir();
if (!fs.existsSync(dir)) {
return { rules: [], enabled };
}
try {
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
const rules = files.map(filename => {
const content = fs.readFileSync(path.join(dir, filename), 'utf-8');
const lines = content.split('\n');
let name = '';
let actualContent = content;
if (lines[0]?.trim().startsWith('# ')) {
name = lines[0].substring(2).trim();
actualContent = lines.slice(2).join('\n').trim();
} else {
name = extractRuleName(content);
}
return { filename, name, content: actualContent };
});
return { rules, enabled };
} catch (error) {
console.error('读取规则失败:', error);
return { rules: [], enabled };
}
}
/**
* 获取当前生效的所有规则内容
*/
export function getActiveRules(): string | null {
const { rules, enabled } = loadPersonalRules();
if (!enabled || rules.length === 0) {
return null;
}
return rules.map(r => r.content).join('\n\n');
}

View File

@ -1,104 +0,0 @@
/**
* TCL 脚本生成器
* 功能:生成 Vivado TCL 脚本
*/
import * as path from 'path';
/**
* 生成创建工程的 TCL 脚本
*/
export function generateCreateProjectTcl(
projectName: string,
projectDir: string,
part: string,
topModule: string,
files: string[],
constraints?: string,
runSynthesis?: boolean
): string {
// 转换路径为 TCL 格式(正斜杠)
const tclPath = (p: string) => p.replace(/\\/g, '/');
let tcl = `# 创建 Vivado 工程\n\n`;
tcl += `create_project ${projectName} {${tclPath(projectDir)}} -part ${part} -force\n\n`;
// 添加源文件
tcl += `# 添加源文件\n`;
files.forEach(file => {
tcl += `add_files -norecurse {${tclPath(file)}}\n`;
});
tcl += `\n`;
// 添加约束文件
if (constraints) {
tcl += `# 添加约束文件\n`;
tcl += `add_files -fileset constrs_1 -norecurse {${tclPath(constraints)}}\n\n`;
}
// 设置顶层模块
tcl += `# 设置顶层模块\n`;
tcl += `set_property top ${topModule} [current_fileset]\n\n`;
if (runSynthesis) {
tcl += `# 执行综合\n`;
tcl += `launch_runs synth_1\n`;
tcl += `wait_on_run synth_1\n\n`;
tcl += `# 打开综合结果\n`;
tcl += `open_run synth_1\n\n`;
tcl += `# 生成报告\n`;
tcl += `report_utilization -file {${tclPath(path.join(projectDir, `${projectName}_utilization.rpt`))}}\n`;
tcl += `report_timing_summary -file {${tclPath(path.join(projectDir, `${projectName}_timing.rpt`))}}\n\n`;
}
return tcl;
}
/**
* 生成综合的 TCL 脚本
*/
export function generateSynthesisTcl(
projectPath: string | undefined,
part: string,
topModule: string,
files?: string[],
constraints?: string,
outputDir?: string
): string {
const tclPath = (p: string) => p.replace(/\\/g, '/');
let tcl = `# Vivado 综合\n\n`;
if (projectPath) {
// 使用现有工程
tcl += `open_project {${tclPath(projectPath)}}\n\n`;
} else {
// 无工程模式
if (!files || files.length === 0) {
throw new Error('无工程模式需要提供源文件');
}
tcl += `# 读取源文件\n`;
files.forEach(file => {
tcl += `read_verilog {${tclPath(file)}}\n`;
});
tcl += `\n`;
if (constraints) {
tcl += `read_xdc {${tclPath(constraints)}}\n\n`;
}
}
tcl += `# 执行综合\n`;
tcl += `synth_design -top ${topModule} -part ${part}\n\n`;
if (outputDir) {
const dcpFile = tclPath(path.join(outputDir, `${topModule}_synth.dcp`));
tcl += `# 保存检查点\n`;
tcl += `write_checkpoint -force {${dcpFile}}\n\n`;
tcl += `# 生成报告\n`;
tcl += `report_utilization -file {${tclPath(path.join(outputDir, `${topModule}_utilization.rpt`))}}\n`;
tcl += `report_timing_summary -file {${tclPath(path.join(outputDir, `${topModule}_timing.rpt`))}}\n`;
}
return tcl;
}

View File

@ -1,87 +0,0 @@
/**
* Vivado 配置管理
* 功能:读取和验证 Vivado 配置
* 依赖vscode
*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
export interface VivadoConfig {
enabled: boolean;
executablePath: string;
workingDir: string;
}
/**
* 获取 Vivado 配置
*/
export function getVivadoConfig(): VivadoConfig | null {
// 优先读取项目配置
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
const projectConfigPath = path.join(
workspaceFolder.uri.fsPath,
'.vscode',
'ic-coder-vivado.json'
);
if (fs.existsSync(projectConfigPath)) {
const content = fs.readFileSync(projectConfigPath, 'utf-8');
return JSON.parse(content).vivado;
}
}
// 读取全局配置
const config = vscode.workspace.getConfiguration('ic-coder');
const vivadoConfig = config.get<VivadoConfig>('vivado');
if (vivadoConfig) {
return vivadoConfig;
}
// 自动检测 Vivado
return autoDetectVivado();
}
/**
* 验证配置
*/
export function validateConfig(config: VivadoConfig): string | null {
if (!config.enabled) {
return 'Vivado 未启用';
}
// 如果是完整路径,检查文件是否存在
if (path.isAbsolute(config.executablePath) && !fs.existsSync(config.executablePath)) {
return `Vivado 可执行文件不存在: ${config.executablePath}`;
}
// 环境变量命令不检查,运行时会自动报错
return null;
}
/**
* 解析工作目录
*/
export function resolveWorkingDir(workingDir: string): string {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
return workingDir.replace('${workspaceFolder}', workspaceFolder.uri.fsPath);
}
return workingDir;
}
/**
* 自动检测 Vivado
*/
function autoDetectVivado(): VivadoConfig | null {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
// 默认使用环境变量中的 vivado 命令
return {
enabled: true,
executablePath: 'vivado',
workingDir: workspaceFolder
? path.join(workspaceFolder.uri.fsPath, 'vivado_projects')
: path.join(process.env.USERPROFILE || 'C:\\Users\\Default', 'vivado_projects')
};
}

View File

@ -1,246 +0,0 @@
/**
* Vivado 执行器
* 功能:执行 Vivado 命令
* 依赖vivadoConfig, tclGenerator
*/
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { spawn } from 'child_process';
import { getVivadoConfig, validateConfig, resolveWorkingDir } from './vivadoConfig';
import { generateCreateProjectTcl, generateSynthesisTcl } from './tclGenerator';
export interface VivadoToolResponse {
success: boolean;
command: string;
executionTime: number;
output: string;
error?: string;
outputFiles?: string[];
reports?: {
resources?: string;
timing?: string;
};
}
/**
* 创建 Vivado 工程
*/
export async function createVivadoProject(params: {
projectName: string;
part: string;
topModule: string;
files: string[];
constraints?: string;
mode: 'gui' | 'batch';
runSynthesis?: boolean;
}): Promise<VivadoToolResponse> {
const startTime = Date.now();
// 读取配置
const config = getVivadoConfig();
if (!config) {
return {
success: false,
command: 'create_project',
executionTime: 0,
output: '',
error: 'Vivado 未配置'
};
}
// 验证配置
const configError = validateConfig(config);
if (configError) {
return {
success: false,
command: 'create_project',
executionTime: 0,
output: '',
error: configError
};
}
// 准备工作目录
const workingDir = resolveWorkingDir(config.workingDir);
if (!fs.existsSync(workingDir)) {
fs.mkdirSync(workingDir, { recursive: true });
}
const projectDir = path.join(workingDir, params.projectName);
// 生成 TCL 脚本
const tclScript = generateCreateProjectTcl(
params.projectName,
projectDir,
params.part,
params.topModule,
params.files,
params.constraints,
params.runSynthesis
);
const tclPath = path.join(workingDir, 'create_project.tcl');
fs.writeFileSync(tclPath, tclScript);
// 执行 Vivado
const result = await executeVivado(
config.executablePath,
tclPath,
workingDir,
params.mode
);
const executionTime = Date.now() - startTime;
// 查找产出文件
const xprFile = path.join(projectDir, `${params.projectName}.xpr`);
const outputFiles = fs.existsSync(xprFile) ? [xprFile] : [];
return {
success: result.success,
command: 'create_project',
executionTime,
output: result.output,
error: result.error,
outputFiles
};
}
/**
* 执行 Vivado 综合
*/
export async function runVivadoSynthesis(params: {
projectPath?: string;
part: string;
topModule: string;
files?: string[];
constraints?: string;
mode: 'gui' | 'batch';
}): Promise<VivadoToolResponse> {
const startTime = Date.now();
const config = getVivadoConfig();
if (!config) {
return {
success: false,
command: 'synthesis',
executionTime: 0,
output: '',
error: 'Vivado 未配置'
};
}
const configError = validateConfig(config);
if (configError) {
return {
success: false,
command: 'synthesis',
executionTime: 0,
output: '',
error: configError
};
}
const workingDir = resolveWorkingDir(config.workingDir);
if (!fs.existsSync(workingDir)) {
fs.mkdirSync(workingDir, { recursive: true });
}
const outputDir = path.join(workingDir, 'synth_output');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const tclScript = generateSynthesisTcl(
params.projectPath,
params.part,
params.topModule,
params.files,
params.constraints,
outputDir
);
const tclPath = path.join(workingDir, 'synthesis.tcl');
fs.writeFileSync(tclPath, tclScript);
const result = await executeVivado(
config.executablePath,
tclPath,
workingDir,
params.mode
);
const executionTime = Date.now() - startTime;
const dcpFile = path.join(outputDir, `${params.topModule}_synth.dcp`);
const utilizationRpt = path.join(outputDir, `${params.topModule}_utilization.rpt`);
const timingRpt = path.join(outputDir, `${params.topModule}_timing.rpt`);
const outputFiles = [];
if (fs.existsSync(dcpFile)) outputFiles.push(dcpFile);
if (fs.existsSync(utilizationRpt)) outputFiles.push(utilizationRpt);
if (fs.existsSync(timingRpt)) outputFiles.push(timingRpt);
return {
success: result.success,
command: 'synthesis',
executionTime,
output: result.output,
error: result.error,
outputFiles
};
}
/**
* 执行 Vivado 命令
*/
async function executeVivado(
executablePath: string,
tclPath: string,
workingDir: string,
mode: 'gui' | 'batch'
): Promise<{ success: boolean; output: string; error?: string }> {
return new Promise((resolve) => {
let output = '';
let errorOutput = '';
const args = mode === 'gui'
? ['-source', tclPath]
: ['-mode', 'batch', '-source', tclPath];
const process = spawn(executablePath, args, {
cwd: workingDir,
shell: true
});
process.stdout.on('data', (data) => {
output += data.toString();
});
process.stderr.on('data', (data) => {
errorOutput += data.toString();
});
process.on('close', (code) => {
if (code === 0) {
resolve({ success: true, output });
} else {
resolve({
success: false,
output,
error: errorOutput || `执行失败,退出码: ${code}`
});
}
});
process.on('error', (err) => {
resolve({
success: false,
output,
error: `启动 Vivado 失败: ${err.message}`
});
});
});
}

View File

@ -1,77 +1,67 @@
import { peopleRules } from "../constants/toolIcons";
/** /**
* 获取规则设置组件的 HTML 内容 * 获取规则设置组件的 HTML 内容
*/ */
export function getRulesSettingsComponentContent(): string { export function getRulesSettingsComponentContent(): string {
return ` return `
<div class="rules-settings"> <div class="rules-settings">
<h3 class="settings-section-title">规则设置</h3> <div class="rules-header">
<h3 class="settings-section-title">个人规则</h3>
<button class="add-rule-button" onclick="showAddRuleModal()">+ 创建</button>
</div>
<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">使用自定义规则来控制 AI 行为</span> <span class="settings-item-description">规则将在每次对话时自动应用</span>
</div> </div>
<label class="settings-switch"> <label class="settings-switch">
<input type="checkbox" id="enableCustomRulesCheckbox" checked> <input type="checkbox" id="enablePersonalRulesCheckbox" checked>
<span class="settings-switch-slider"></span> <span class="settings-switch-slider"></span>
</label> </label>
</div> </div>
</div> </div>
<div class="settings-section"> <div class="rules-list" id="rulesList">
<h4 class="settings-subsection-title">系统规则</h4> <!-- 规则列表将动态插入这里 -->
<div class="rules-textarea-container"> </div>
<!-- 添加/编辑规则弹窗 -->
<div class="rule-modal" id="ruleModal" style="display: none;">
<div class="rule-modal-content">
<h4 id="modalTitle">创建个人规则</h4>
<input
type="text"
class="rule-name-input"
id="ruleNameInput"
placeholder="规则名称"
/>
<textarea <textarea
class="rules-textarea" class="rule-textarea"
id="systemRulesTextarea" id="ruleTextarea"
placeholder="在此输入系统规则,例如:&#10;- 始终使用中文回复&#10;- 代码注释要详细&#10;- 遵循项目编码规范" placeholder="输入规则内容..."
rows="8" rows="10"
></textarea> ></textarea>
<div class="rules-textarea-hint"> <div class="rule-modal-actions">
系统规则会在每次对话开始时应用 <button class="settings-button settings-button-primary" onclick="saveRule()">保存</button>
<button class="settings-button settings-button-secondary" onclick="closeRuleModal()">取消</button>
</div> </div>
</div> </div>
</div> </div>
<div class="settings-section"> <!-- 删除确认弹窗 -->
<h4 class="settings-subsection-title">代码生成规则</h4> <div class="rule-modal" id="deleteConfirmModal" style="display: none;">
<div class="rules-textarea-container"> <div class="rule-modal-content" style="width: 400px;">
<textarea <h4>确认删除</h4>
class="rules-textarea" <p id="deleteConfirmText" style="color: var(--vscode-foreground); margin: 16px 0;"></p>
id="codeRulesTextarea" <div class="rule-modal-actions">
placeholder="在此输入代码生成规则,例如:&#10;- 使用 TypeScript 严格模式&#10;- 函数命名使用驼峰命名法&#10;- 添加必要的错误处理" <button class="settings-button settings-button-primary" onclick="confirmDelete()">确定</button>
rows="8" <button class="settings-button settings-button-secondary" onclick="closeDeleteConfirmModal()">取消</button>
></textarea>
<div class="rules-textarea-hint">
这些规则会在生成代码时应用
</div> </div>
</div> </div>
</div> </div>
<div class="settings-section">
<h4 class="settings-subsection-title">Verilog 规则</h4>
<div class="rules-textarea-container">
<textarea
class="rules-textarea"
id="verilogRulesTextarea"
placeholder="在此输入 Verilog 代码规则,例如:&#10;- 使用非阻塞赋值 (<=) 在时序逻辑中&#10;- 模块命名使用小写加下划线&#10;- 添加详细的端口注释"
rows="8"
></textarea>
<div class="rules-textarea-hint">
这些规则会在生成 Verilog 代码时应用
</div>
</div>
</div>
<div class="settings-actions">
<button class="settings-button settings-button-primary" onclick="saveRulesSettings()">
保存规则
</button>
<button class="settings-button settings-button-secondary" onclick="resetRulesSettings()">
重置为默认
</button>
</div>
</div> </div>
`; `;
} }
@ -85,11 +75,144 @@ export function getRulesSettingsComponentStyles(): string {
max-width: 700px; max-width: 700px;
} }
.rules-textarea-container { .rules-header {
margin-top: 8px; display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
} }
.rules-textarea { .add-rule-button {
padding: 6px 12px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
.add-rule-button:hover {
background: var(--vscode-button-hoverBackground);
}
.rules-list {
margin-top: 16px;
}
.rule-item {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
padding: 12px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.rule-item-name {
color: var(--vscode-foreground);
font-size: 13px;
}
.rule-item > div:first-child svg {
background-color: rgba(148, 204, 241, 0.3);
border-radius: 4px;
padding: 4px;
}
.rule-item-menu {
position: relative;
}
.rule-menu-icon {
width: 20px;
height: 20px;
cursor: pointer;
padding: 4px;
border-radius: 3px;
}
.rule-menu-icon:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.rule-dropdown {
position: absolute;
right: 0;
top: 28px;
background: var(--vscode-menu-background);
border: 1px solid var(--vscode-menu-border);
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
z-index: 100;
min-width: 100px;
}
.rule-dropdown button {
display: block;
width: 100%;
padding: 8px 12px;
background: transparent;
color: var(--vscode-menu-foreground);
border: none;
text-align: left;
cursor: pointer;
font-size: 13px;
}
.rule-dropdown button:hover {
background: var(--vscode-menu-selectionBackground);
color: var(--vscode-menu-selectionForeground);
}
.rule-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.rule-modal-content {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-input-border);
border-radius: 6px;
padding: 20px;
width: 500px;
max-width: 90%;
}
.rule-modal-content h4 {
margin: 0 0 16px 0;
color: var(--vscode-foreground);
}
.rule-name-input {
width: 100%;
padding: 8px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
font-size: 13px;
margin-bottom: 12px;
box-sizing: border-box;
}
.rule-name-input:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.rule-textarea {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
background: var(--vscode-input-background); background: var(--vscode-input-background);
@ -98,26 +221,20 @@ export function getRulesSettingsComponentStyles(): string {
border-radius: 4px; border-radius: 4px;
font-size: 13px; font-size: 13px;
font-family: var(--vscode-editor-font-family); font-family: var(--vscode-editor-font-family);
line-height: 1.5;
resize: vertical; resize: vertical;
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;
} }
.rules-textarea:focus { .rule-textarea:focus {
border-color: var(--vscode-focusBorder); border-color: var(--vscode-focusBorder);
} }
.rules-textarea::placeholder { .rule-modal-actions {
color: var(--vscode-input-placeholderForeground); display: flex;
opacity: 0.6; gap: 8px;
} margin-top: 16px;
justify-content: flex-end;
.rules-textarea-hint {
margin-top: 8px;
font-size: 12px;
color: var(--vscode-descriptionForeground);
font-style: italic;
} }
`; `;
} }
@ -127,51 +244,163 @@ export function getRulesSettingsComponentStyles(): string {
*/ */
export function getRulesSettingsComponentScript(): string { export function getRulesSettingsComponentScript(): string {
return ` return `
// 保存规则设置 let currentRules = [];
function saveRulesSettings() { let editingRule = null;
const settings = { let deletingFilename = null;
enableCustomRules: document.getElementById('enableCustomRulesCheckbox').checked,
systemRules: document.getElementById('systemRulesTextarea').value,
codeRules: document.getElementById('codeRulesTextarea').value,
verilogRules: document.getElementById('verilogRulesTextarea').value,
};
// 发送消息到扩展 // 显示添加规则弹窗
vscode.postMessage({ function showAddRuleModal() {
command: 'saveRulesSettings', editingRule = null;
settings: settings document.getElementById('modalTitle').textContent = '创建个人规则';
}); document.getElementById('ruleNameInput').value = '';
document.getElementById('ruleTextarea').value = '';
// 显示保存成功提示 document.getElementById('ruleModal').style.display = 'flex';
console.log('规则设置已保存', settings);
} }
// 重置规则设置 // 关闭弹窗
function resetRulesSettings() { function closeRuleModal() {
document.getElementById('enableCustomRulesCheckbox').checked = true; document.getElementById('ruleModal').style.display = 'none';
document.getElementById('systemRulesTextarea').value = ''; closeAllDropdowns();
document.getElementById('codeRulesTextarea').value = '';
document.getElementById('verilogRulesTextarea').value = '';
console.log('规则设置已重置为默认值');
} }
// 加载规则设置 // 切换下拉菜单
function loadRulesSettings(settings) { function toggleDropdown(filename, event) {
if (!settings) return; event.stopPropagation();
closeAllDropdowns();
if (settings.enableCustomRules !== undefined) { const dropdown = document.getElementById('dropdown-' + filename);
document.getElementById('enableCustomRulesCheckbox').checked = settings.enableCustomRules; if (dropdown) {
} dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
if (settings.systemRules) {
document.getElementById('systemRulesTextarea').value = settings.systemRules;
}
if (settings.codeRules) {
document.getElementById('codeRulesTextarea').value = settings.codeRules;
}
if (settings.verilogRules) {
document.getElementById('verilogRulesTextarea').value = settings.verilogRules;
} }
} }
// 关闭所有下拉菜单
function closeAllDropdowns() {
document.querySelectorAll('.rule-dropdown').forEach(d => d.style.display = 'none');
}
// 点击页面其他地方关闭下拉菜单
document.addEventListener('click', closeAllDropdowns);
// 编辑规则
function editRule(filename) {
const rule = currentRules.find(r => r.filename === filename);
if (rule) {
editingRule = rule;
document.getElementById('modalTitle').textContent = '修改个人规则';
document.getElementById('ruleNameInput').value = rule.name;
document.getElementById('ruleTextarea').value = rule.content;
document.getElementById('ruleModal').style.display = 'flex';
}
}
// 保存规则
function saveRule() {
const name = document.getElementById('ruleNameInput').value.trim();
const content = document.getElementById('ruleTextarea').value.trim();
if (!name) {
alert('规则名称不能为空');
return;
}
if (!content) {
alert('规则内容不能为空');
return;
}
const enabled = document.getElementById('enablePersonalRulesCheckbox').checked;
if (editingRule) {
vscode.postMessage({
command: 'updatePersonalRule',
filename: editingRule.filename,
name: name,
content: content,
enabled: enabled
});
} else {
vscode.postMessage({
command: 'savePersonalRule',
name: name,
content: content,
enabled: enabled
});
}
closeRuleModal();
}
// 删除规则
function deleteRule(filename) {
closeAllDropdowns();
const rule = currentRules.find(r => r.filename === filename);
const ruleName = rule ? rule.name : filename;
deletingFilename = filename;
document.getElementById('deleteConfirmText').textContent = '确定要删除规则"' + ruleName + '"吗?此操作无法撤销。';
document.getElementById('deleteConfirmModal').style.display = 'flex';
}
// 关闭删除确认弹窗
function closeDeleteConfirmModal() {
document.getElementById('deleteConfirmModal').style.display = 'none';
deletingFilename = null;
}
// 确认删除
function confirmDelete() {
if (deletingFilename) {
vscode.postMessage({
command: 'deletePersonalRule',
filename: deletingFilename
});
}
closeDeleteConfirmModal();
}
// 渲染规则列表
function renderRulesList(rules) {
currentRules = rules || [];
const listEl = document.getElementById('rulesList');
if (currentRules.length === 0) {
listEl.innerHTML = '<div style="color: var(--vscode-descriptionForeground); padding: 16px; text-align: center;">暂无规则,点击"+ 创建"添加</div>';
return;
}
const peopleRulesIcon = '${peopleRules}';
listEl.innerHTML = currentRules.map(rule => \`
<div class="rule-item">
<div style="display: flex; align-items: center; gap: 8px;">
\${peopleRulesIcon}
<div class="rule-item-name">\${rule.filename}</div>
</div>
<div class="rule-item-menu">
<svg class="rule-menu-icon" onclick="toggleDropdown('\${rule.filename}', event)" viewBox="0 0 16 16" fill="currentColor">
<circle cx="8" cy="3" r="1.5"/>
<circle cx="8" cy="8" r="1.5"/>
<circle cx="8" cy="13" r="1.5"/>
</svg>
<div class="rule-dropdown" id="dropdown-\${rule.filename}" style="display: none;">
<button onclick="editRule('\${rule.filename}')">编辑</button>
<button onclick="deleteRule('\${rule.filename}')">删除</button>
</div>
</div>
</div>
\`).join('');
}
// 加载规则列表
function loadPersonalRules(data) {
if (data && data.enabled !== undefined) {
document.getElementById('enablePersonalRulesCheckbox').checked = data.enabled;
}
if (data && data.rules) {
renderRulesList(data.rules);
}
}
// 页面加载时请求规则数据
vscode.postMessage({ command: 'loadPersonalRules' });
`; `;
} }

View File

@ -737,6 +737,13 @@ export function getWebviewContent(
} }
break; break;
case 'personalRulesLoaded':
// 加载个人规则数据
if (typeof loadPersonalRules === 'function') {
loadPersonalRules(message.data);
}
break;
case 'autoSendMessage': case 'autoSendMessage':
// 自动发送待发送的消息(登录后) // 自动发送待发送的消息(登录后)
console.log('[WebView] 自动发送待发送消息:', message.text); console.log('[WebView] 自动发送待发送消息:', message.text);