20 Commits

Author SHA1 Message Date
1a91513031 Merge branch 'feat/codeToChat' into feat/Knowledge-Base 2026-03-20 15:19:32 +08:00
69f86dbc0d fix:修复启用/禁用个人规则失效的bug 2026-03-20 15:19:24 +08:00
24512c61e6 style:解决不同主题下颜色变化的bug
- AI询问用户组件
2026-03-20 14:05:08 +08:00
1207d2b91a feat:实现携带路径发送信息的优化
- 将路径通过doc包裹起来便于后端读取
2026-03-20 11:45:01 +08:00
cc4583e2cc style:修改代码高亮的margin 2026-03-19 16:18:01 +08:00
4ba096d898 feat:实现文档集同步的功能 2026-03-19 10:36:13 +08:00
d2cd7b0bc8 feat:新增文档集修改名称的功能 2026-03-19 09:43:50 +08:00
686aaebc26 feat:文档集文件大小和个数限制
- 单个文件不能大于10MB
- 总文件不能大于100MB
- 总文件个数不能超过1000个
2026-03-19 09:23:55 +08:00
894479e252 Merge branch 'feat/codeToChat' into feat/Knowledge-Base 2026-03-17 17:28:53 +08:00
cab8960159 docs: 添加文档集功能需求文档
详细描述了文档集功能的核心流程、前后端需求和交互逻辑
2026-03-17 17:28:37 +08:00
46233d2ac3 feat:实现文档集删除功能
- 文档集按钮添加tooltip
- 实现删除的二次确认弹窗
2026-03-17 17:20:02 +08:00
79a6ff4c99 feat: 实现文档集持久化存储
- 在 extension.ts 中初始化 contextHelper
   - 在 ICHelperPanel.ts 中加载已保存的文档集
   - 在 contextHelper.ts 中实现持久化逻辑
     - 使用 globalState 存储文档集数据
     - 保存和删除时自动持久化
2026-03-17 17:06:23 +08:00
ada3a3bffd feat: 完善文档集管理功能
- 新增文档集保存功能,支持命名和时间戳
   - 实现文档集删除功能
   - 添加文档集列表展示界面
   - 优化文档集 UI 样式和交互
2026-03-17 16:44:39 +08:00
1d64310607 Update:changelog 2026-03-17 16:10:14 +08:00
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
64cce80a70 feat: 实现文档集管理系统
新增功能:
   - 文档集创建:支持添加 .md/.txt/.v/.sv/.pdf 格式文件
   - 智能限制:单文件 10MB,总容量 50MB,最多 1000 个文件
   - 状态管理:实时显示索引状态(待索引/索引中/已索引)
   - 交互优化:支持文件预览、删除操作,带 loading 动画

   技术实现:
   - 新增 docsetDialog 组件处理文档集对话框
   - 新增 contextSettingsComponent 管理上下文设置
   - 扩展 contextHelper 支持文档集 CRUD 操作
   - 优化 messageRouter 处理文档集相关消息
2026-03-17 14:28:28 +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
25 changed files with 2580 additions and 147 deletions

View File

@ -2,6 +2,50 @@
所有重要的项目变更都将记录在此文件中。 所有重要的项目变更都将记录在此文件中。
## [1.13.5] - 2026-03-17
### 新增
- 个人规则管理功能
- 代码选择添加到对话Ctrl+L / Cmd+L
### 优化
- 消息处理模块重构
- 提示信息优化
## [1.13.0] - 2026-03-15
### 新增
- Vivado 联动功能文档
- 示例刷新按钮
### 优化
- 界面样式和交互体验
## [1.12.0] - 2026-03-12
### 新增
- 对话结束提示
### 优化
- 面板组件架构优化
## [1.11.0] - 2026-03-10
### 优化
- 代码选择交互优化
### 修复
- AI 提问输入问题
- Windows 平台兼容性问题
## [1.0.12] - 2026-03-06 ## [1.0.12] - 2026-03-06
### 新增 ### 新增

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

@ -0,0 +1,87 @@
# 文档集功能需求文档
## 1. 功能概述
文档集功能允许用户管理文档,在添加上下文时可以选择文档加载到输入框中,发送给 AI 作为对话上下文。
## 2. 核心流程
### 2.1 添加文档集入口
1. 用户点击"添加上下文"中的"文档"按钮
2. 如果文档集为空,显示"添加文档集"按钮
3. 点击按钮跳转到"设置 → 上下文"页面
### 2.2 创建文档集
1. 在设置的上下文页面,点击"添加文档集"按钮
2. 弹出文档集创建对话框
3. 用户输入文档集名称
4. 用户点击"添加文件"按钮,选择文件
5. 系统验证文件(格式、大小、数量)
6. 显示已选文件列表和统计信息
7. 用户点击"确定",保存文档集
8. 系统持久化文档集信息到 `globalState`
### 2.3 使用文档
1. 用户点击"添加上下文"中的"文档"按钮
2. 显示所有文档列表(自动同步设置中的文档集)
3. 用户点击选择一个或多个文档
4. 文档路径加载到输入框中
5. 用户发送消息,后端读取文档内容作为上下文
### 2.4 管理文档集
1. 在设置的上下文页面查看文档列表
2. 显示每个文档的更新时间
3. 支持修改文档名称
4. 支持删除文档
## 3. 功能详细需求
### 3.1 前端需求
#### 3.1.1 添加上下文 - 文档按钮
**功能**:
- 点击"文档"按钮,显示文档列表弹窗
- 如果没有文档,显示"添加文档集"按钮
- 点击"添加文档集"按钮,跳转到设置的上下文页面
#### 3.1.2 文档列表弹窗
**UI 元素**:
- 文档列表(显示所有文档集中的文档)
- 每个文档显示:名称
- 支持多选
- 确定/取消按钮
**交互逻辑**:
- 自动同步设置中的文档集
- 点击文档选中/取消选中
- 点击确定,将选中文档路径加载到输入框
#### 3.1.3 设置 - 上下文页面
**UI 元素**:
- "添加文档集"按钮
- 文档列表
- 每个文档显示:名称、更新时间、修改名称按钮、删除按钮
**交互逻辑**:
- 点击"添加文档集"打开创建对话框
- 点击修改名称,弹出输入框修改
- 点击删除,删除二次确认弹窗 确认删除文档
#### 3.1.4 文档集创建对话框
**UI 元素**:
- 文档集名称输入框
- 添加文件按钮
- 文件列表显示区域
- 确定/取消按钮
**交互逻辑**:
- 点击"添加文件"触发文件选择器
- 显示已选文件的相对路径和大小
- 支持删除单个文件
- 实时更新统计信息
### 3.2 后端需求
后端只需要支持读取pdf,txt,.v,.sv.md这些类型的文档

View File

@ -2,7 +2,7 @@
"name": "iccoder", "name": "iccoder",
"displayName": "IC Coder: Agentic Verilog Platform", "displayName": "IC Coder: Agentic Verilog Platform",
"description": "Agentic Verilog Coding Platform for Real-World FPGAs", "description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "1.0.12", "version": "1.13.5",
"publisher": "ICCoderAgenticVerilogPlatform", "publisher": "ICCoderAgenticVerilogPlatform",
"engines": { "engines": {
"vscode": "^1.80.0" "vscode": "^1.80.0"
@ -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

@ -34,7 +34,7 @@ export function getCodeHighlightStyles(): string {
border-radius: 6px; border-radius: 6px;
padding: 12px; padding: 12px;
overflow-x: auto; overflow-x: auto;
margin: 12px 0; margin: 52px 0 12px 0;
position: relative; position: relative;
white-space: pre; white-space: pre;
} }

View File

@ -8,7 +8,7 @@ import * as vscode from "vscode";
type Environment = "dev" | "test" | "prod"; type Environment = "dev" | "test" | "prod";
/** 当前环境 - 修改这里切换环境 */ /** 当前环境 - 修改这里切换环境 */
const CURRENT_ENV: Environment = "prod"; const CURRENT_ENV: Environment = "test";
/** 服务等级类型 */ /** 服务等级类型 */
export type ServiceTier = "lite" | "syntaxic" | "max" | "auto"; export type ServiceTier = "lite" | "syntaxic" | "max" | "auto";
@ -42,9 +42,9 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
}, },
/** 测试服务器环境 - 通过 Gateway 路由 */ /** 测试服务器环境 - 通过 Gateway 路由 */
test: { test: {
backendUrl: "http://192.168.1.108:2029/iccoder", backendUrl: "http://192.168.1.134:2233",
backendUrlStrongeLoop: "http://192.168.1.108:2029", backendUrlStrongeLoop: "http://192.168.1.134:2233",
loginUrl: "http://192.168.1.108:2005/login", loginUrl: "http://192.168.1.134/login",
timeout: 60000, timeout: 60000,
userId: "default-user", userId: "default-user",
serviceTier: "max", serviceTier: "max",

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

@ -11,10 +11,13 @@ 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 { ICCoderCodeActionProvider } from "./providers/codeActionProvider";
import { initializeContextHelper } from "./panels/helpers/contextHelper";
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
console.log("🎉 IC Coder 插件已激活!"); console.log("🎉 IC Coder 插件已激活!");
initializeContextHelper(context);
// 创建装饰类型(代码旁边的提示) // 创建装饰类型(代码旁边的提示)
const decorationType = vscode.window.createTextEditorDecorationType({ const decorationType = vscode.window.createTextEditorDecorationType({
after: { after: {

View File

@ -13,6 +13,7 @@ import {
setupBalanceUpdateCallback, setupBalanceUpdateCallback,
} from "./helpers/userInfoHelper"; } from "./helpers/userInfoHelper";
import { handleWebviewMessage } from "./helpers/messageRouter"; import { handleWebviewMessage } from "./helpers/messageRouter";
import { getDocumentSets } from "./helpers/contextHelper";
function getIconUris( function getIconUris(
webview: vscode.Webview, webview: vscode.Webview,
@ -123,6 +124,13 @@ export async function showICHelperPanel(
await sendUserInfoToWebview(panel, context); await sendUserInfoToWebview(panel, context);
setupBalanceUpdateCallback(panel, context); setupBalanceUpdateCallback(panel, context);
setTimeout(() => {
panel.webview.postMessage({
command: "documentSetSaved",
documentSets: getDocumentSets(),
});
}, 500);
const pendingMessage = context.globalState.get("pendingMessage") as any; const pendingMessage = context.globalState.get("pendingMessage") as any;
if (pendingMessage) { if (pendingMessage) {
await context.globalState.update("pendingMessage", undefined); await context.globalState.update("pendingMessage", undefined);

View File

@ -6,6 +6,27 @@
*/ */
import * as vscode from "vscode"; import * as vscode from "vscode";
let globalContext: vscode.ExtensionContext;
const DOCUMENT_SETS_KEY = "iccoder.documentSets";
export interface DocumentFile {
name: string;
absolutePath: string;
size: number;
}
export interface DocumentSet {
id: string;
name: string;
files: DocumentFile[];
updatedAt: number;
}
export function initializeContextHelper(context: vscode.ExtensionContext) {
globalContext = context;
loadDocumentSets();
}
export async function handleAddContextFile(panel: vscode.WebviewPanel) { export async function handleAddContextFile(panel: vscode.WebviewPanel) {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) { if (!workspaceFolder) {
@ -102,3 +123,126 @@ export async function handleAddContextDocument(panel: vscode.WebviewPanel) {
}); });
} }
} }
export async function handleAddContextDocumentSet(panel: vscode.WebviewPanel) {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
vscode.window.showWarningMessage("请先打开一个工作区");
return;
}
const files = await vscode.workspace.findFiles(
"**/*.{md,txt,pdf,doc,docx}",
"**/node_modules/**",
);
panel.webview.postMessage({
command: "showWorkspaceDocumentSetList",
files: files.map((uri) => ({
path: uri.fsPath,
relativePath: vscode.workspace.asRelativePath(uri),
})),
});
}
export async function handleGetDocumentSetList(panel: vscode.WebviewPanel) {
panel.webview.postMessage({
command: "showDocumentSetList",
documents: documentSets,
});
}
let documentSet: DocumentFile[] = [];
export function getDocumentSet() {
return documentSet;
}
export function addToDocumentSet(docs: DocumentFile[]) {
documentSet = [...documentSet, ...docs];
}
export function saveDocumentSet(
docs: DocumentFile[],
name: string,
panel: vscode.WebviewPanel
) {
const newDocSet: DocumentSet = {
id: Date.now().toString(),
name,
files: docs,
updatedAt: Date.now(),
};
documentSets.push(newDocSet);
persistDocumentSets();
panel.webview.postMessage({
command: "documentSetSaved",
documentSets: documentSets,
});
}
let documentSets: DocumentSet[] = [];
function loadDocumentSets() {
const saved =
globalContext.globalState.get<Array<any>>(DOCUMENT_SETS_KEY) ||
globalContext.globalState.get<Array<any>>("documentSets", []);
if (saved) {
documentSets = saved.map((item: any) => ({
id: String(item.id),
name: String(item.name || ""),
files: Array.isArray(item.files)
? item.files.map((file: any) => ({
name: String(file.name || ""),
absolutePath: String(file.absolutePath || file.path || ""),
size: Number(file.size || 0),
}))
: Array.isArray(item.documents)
? item.documents.map((file: any) => ({
name: String(
file.name ||
file.relativePath?.split(/[\\/]/).pop() ||
file.path?.split(/[\\/]/).pop() ||
"",
),
absolutePath: String(file.absolutePath || file.path || ""),
size: Number(file.size || 0),
}))
: [],
updatedAt:
typeof item.updatedAt === "number"
? item.updatedAt
: new Date(item.updatedAt || Date.now()).getTime(),
}));
}
}
function persistDocumentSets() {
globalContext.globalState.update(DOCUMENT_SETS_KEY, documentSets);
}
export function getDocumentSets() {
return documentSets;
}
export function deleteDocumentSet(id: string, panel: vscode.WebviewPanel) {
documentSets = documentSets.filter(ds => ds.id !== id);
persistDocumentSets();
panel.webview.postMessage({
command: "documentSetSaved",
documentSets: documentSets,
});
}
export function changeDocumentSetName(id: string, newName: string, panel: vscode.WebviewPanel) {
const docSet = documentSets.find(ds => ds.id === id);
if (docSet) {
docSet.name = newName;
docSet.updatedAt = Date.now();
persistDocumentSets();
panel.webview.postMessage({
command: "documentSetSaved",
documentSets: documentSets,
});
}
}

View File

@ -22,16 +22,28 @@ import {
handleOpenFileDiff, handleOpenFileDiff,
startChangeSession, startChangeSession,
} from "../../utils/messageHandler"; } from "../../utils/messageHandler";
import {
loadPersonalRules,
savePersonalRule,
updatePersonalRule,
deletePersonalRule,
updatePersonalRulesEnabled,
} 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";
import { loadConversationHistory, selectConversation } from "./conversationHelper"; import {
loadConversationHistory,
selectConversation,
} from "./conversationHelper";
import { getVCDFileInfo } from "./vcdHelper"; import { getVCDFileInfo } from "./vcdHelper";
import { import {
handleAddContextFile, handleAddContextFile,
handleAddContextFolder, handleAddContextFolder,
handleAddContextImage, handleAddContextImage,
handleAddContextDocument, handleAddContextDocument,
handleAddContextDocumentSet,
handleGetDocumentSetList,
} from "./contextHelper"; } from "./contextHelper";
import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper"; import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper";
@ -130,16 +142,16 @@ export async function handleWebviewMessage(
break; break;
case "loadConversationHistory": case "loadConversationHistory":
loadConversationHistory( loadConversationHistory(panel, message.offset || 0, message.limit || 10);
panel,
message.offset || 0,
message.limit || 10,
);
break; break;
case "selectConversation": case "selectConversation":
if (message.conversationId) { if (message.conversationId) {
selectConversation(panel, message.conversationId, context.extensionPath); selectConversation(
panel,
message.conversationId,
context.extensionPath,
);
} }
break; break;
@ -253,7 +265,9 @@ export async function handleWebviewMessage(
verified: true, verified: true,
}); });
} else { } else {
const { InvitationService } = require("../../services/invitationService"); const {
InvitationService,
} = require("../../services/invitationService");
const isVerified = await InvitationService.isVerified(context); const isVerified = await InvitationService.isVerified(context);
panel.webview.postMessage({ panel.webview.postMessage({
command: "invitationCodeStatus", command: "invitationCodeStatus",
@ -284,7 +298,9 @@ export async function handleWebviewMessage(
case "checkTrialExpiration": case "checkTrialExpiration":
{ {
const { TrialExpirationService } = require("../../services/trialExpirationService"); const {
TrialExpirationService,
} = require("../../services/trialExpirationService");
const trialService = new TrialExpirationService(context, panel); const trialService = new TrialExpirationService(context, panel);
await trialService.checkExpiration(); await trialService.checkExpiration();
} }
@ -292,7 +308,9 @@ export async function handleWebviewMessage(
case "verifyInvitationCode": case "verifyInvitationCode":
{ {
const { InvitationService } = require("../../services/invitationService"); const {
InvitationService,
} = require("../../services/invitationService");
const result = await InvitationService.verifyCode(message.code); const result = await InvitationService.verifyCode(message.code);
if (result.success) { if (result.success) {
@ -356,6 +374,104 @@ export async function handleWebviewMessage(
await handleAddContextFolder(panel); await handleAddContextFolder(panel);
break; break;
case "addContextDocumentSet":
await handleAddContextDocumentSet(panel);
break;
case "getDocumentSetList":
await handleGetDocumentSetList(panel);
break;
case "saveDocumentSet":
const { saveDocumentSet } = await import("./contextHelper");
saveDocumentSet(message.documents || [], message.name || "", panel);
break;
case "deleteDocumentSet":
const { deleteDocumentSet } = await import("./contextHelper");
deleteDocumentSet(message.id, panel);
break;
case "changeDocumentSetName":
const { changeDocumentSetName } = await import("./contextHelper");
changeDocumentSetName(message.id, message.newName, panel);
break;
case "openContextSettings":
panel.webview.postMessage({
command: "openSettingsTab",
tab: "context",
});
break;
case "selectFilesForDocset":
const uris = await vscode.window.showOpenDialog({
canSelectMany: true,
canSelectFiles: true,
canSelectFolders: false,
openLabel: "选择文件",
filters: {
: ["md", "txt", "v", "sv", "pdf"],
},
});
if (uris && uris.length > 0) {
const fs = require("fs");
const path = require("path");
const selectedFiles: Array<{
name: string;
absolutePath: string;
size: number;
}> = [];
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
const MAX_TOTAL_SIZE = 50 * 1024 * 1024; // 50 MB
const MAX_FILES = 1000;
let totalSize = 0;
const errors: string[] = [];
for (const uri of uris) {
const filePath = uri.fsPath;
const ext = path.extname(filePath).toLowerCase();
if (![".md", ".txt", ".v", ".sv", ".pdf"].includes(ext)) {
errors.push(`文件 ${path.basename(filePath)} 格式不支持`);
continue;
}
try {
const stat = fs.statSync(filePath);
if (stat.size > MAX_FILE_SIZE) {
errors.push(`文件 ${path.basename(filePath)} 超过 10 MB`);
continue;
}
if (totalSize + stat.size > MAX_TOTAL_SIZE) {
errors.push("文档集总大小超过 50 MB");
break;
}
if (selectedFiles.length >= MAX_FILES) {
errors.push("文件数量超过 1000 个");
break;
}
selectedFiles.push({
name: path.basename(filePath),
absolutePath: filePath,
size: stat.size,
});
totalSize += stat.size;
} catch (err) {
errors.push(`无法读取文件 ${path.basename(filePath)}`);
}
}
panel.webview.postMessage({
command: "filesSelectedForDocset",
files: selectedFiles,
errors: errors.length > 0 ? errors : undefined,
});
}
break;
case "addContextImage": case "addContextImage":
await handleAddContextImage(panel); await handleAddContextImage(panel);
break; break;
@ -392,5 +508,84 @@ 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;
case "updatePersonalRulesEnabled":
{
const success = await updatePersonalRulesEnabled(message.enabled);
if (success) {
const data = loadPersonalRules();
panel.webview.postMessage({
command: "personalRulesLoaded",
data: data,
});
}
}
break;
case "loadDocumentSets":
const { getDocumentSets } = await import("./contextHelper");
panel.webview.postMessage({
command: "documentSetSaved",
documentSets: getDocumentSets(),
});
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

@ -162,6 +162,8 @@ 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] 开始流式对话: taskId=${request.taskId}, mode=${request.mode}, url=${urlString}`);
console.log("[SSE] 完整请求体:", request);
console.log("[SSE] 请求体 JSON:", body);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const options: http.RequestOptions = { const options: http.RequestOptions = {

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

@ -268,8 +268,19 @@ 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 docTypes = new Set(["file", "document", "docset"]);
enhancedText = `${paths}\n\n${text}`; const regularPaths = contextItems
.filter((item) => !docTypes.has(item.type))
.map((item) => item.path);
const docTags = contextItems
.filter((item) => docTypes.has(item.type))
.map((item) => `<doc>${item.path}</doc>`);
if (regularPaths.length > 0) {
enhancedText = `${regularPaths.join("\n")}\n\n${enhancedText}`;
}
if (docTags.length > 0) {
enhancedText = `${enhancedText}\n\n${docTags.join("\n")}`;
}
} }
// 获取 historyManager 中的 taskId由 ICHelperPanel 创建) // 获取 historyManager 中的 taskId由 ICHelperPanel 创建)
@ -307,8 +318,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;
@ -427,7 +438,7 @@ async function handleUserMessageWithBackend(
}); });
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `错误: ${message}`, text: `服务繁忙,请稍后重试`,
}); });
// 恢复输入状态 // 恢复输入状态
panel.webview.postMessage({ panel.webview.postMessage({
@ -848,10 +859,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(`服务繁忙,请稍后重试`);
} }
} }

View File

@ -0,0 +1,194 @@
/**
* 个人规则管理工具
* 功能:读写个人规则文件
* 依赖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 async function updatePersonalRulesEnabled(
enabled: boolean,
): Promise<boolean> {
try {
await vscode.workspace
.getConfiguration("ic-coder")
.update(
"personalRulesEnabled",
enabled,
vscode.ConfigurationTarget.Global,
);
return true;
} catch (error) {
console.error("更新规则启用状态失败:", error);
return false;
}
}
/**
* 获取当前生效的所有规则内容
*/
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

@ -41,18 +41,15 @@ export function getContextButtonContent(): string {
<path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/> <path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/>
</svg> </svg>
</div> </div>
<!-- <div class="context-menu-item" onclick="handleAddImage()"> <div class="context-menu-item" onclick="handleAddDocumentSet()">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 632H136V232h752v560z m-120-240c0 55.2-44.8 100-100 100s-100-44.8-100-100 44.8-100 100-100 100 44.8 100 100z m-476 0l164 164h476L696 480 536 640l-84-84-160 160z" fill="currentColor"/>
</svg>
<span>图片</span>
</div> -->
<!-- <div class="context-menu-item" onclick="handleAddDocument()">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"> <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 m-40 824H232V136h560v752z m-120-568H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z m0 144H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z m0 144H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z" fill="currentColor"/> <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 m-40 824H232V136h560v752z m-120-568H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z m0 144H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z m0 144H352c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h320c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z" fill="currentColor"/>
</svg> </svg>
<span>文档</span> <span>文档</span>
</div> --> <svg class="arrow-right" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M340.864 149.312l384 384-384 384-45.248-45.248L634.368 533.312 295.616 194.56z" fill="currentColor"/>
</svg>
</div>
</div> </div>
<!-- 文件/文件夹列表视图 --> <!-- 文件/文件夹列表视图 -->
@ -382,6 +379,48 @@ export function getContextButtonScript(): string {
vscode.postMessage({ command: 'addContextFolder' }); vscode.postMessage({ command: 'addContextFolder' });
} }
// 显示文档集列表
function showDocumentSetList() {
vscode.postMessage({ command: 'getDocumentSetList' });
}
// 显示文档集视图
function showDocumentSetView(documents) {
const mainMenu = document.getElementById('contextMenuMain');
const listView = document.getElementById('contextMenuList');
const titleEl = document.getElementById('contextMenuListTitle');
const bodyEl = document.getElementById('contextMenuListBody');
if (mainMenu && listView && titleEl && bodyEl) {
mainMenu.style.display = 'none';
listView.style.display = 'flex';
titleEl.textContent = '文档集';
if (documents.length === 0) {
bodyEl.innerHTML = \`
<div class="context-empty" style="padding: 40px 20px; text-align: center;">
<p style="margin: 0 0 12px 0; color: var(--vscode-descriptionForeground);">暂无文档</p>
<button class="context-add-empty-btn" onclick="addDocumentToSet()" style="padding: 8px 20px; background: transparent; color: var(--vscode-textLink-foreground); border: 1px solid var(--vscode-textLink-foreground); border-radius: 4px; cursor: pointer; font-size: 13px;">添加文档集</button>
</div>
\`;
} else {
currentListType = 'documentSetItem';
currentListData = documents;
filteredListData = documents;
selectedItems.clear();
renderDocumentSetList(documents);
}
}
}
// 添加文档到文档集
function addDocumentToSet() {
vscode.postMessage({ command: 'openContextSettings' });
toggleContextMenu();
}
// 添加文档集项到上下文(已删除,使用统一的确认选择)
// 返回主菜单 // 返回主菜单
function backToMainMenu() { function backToMainMenu() {
const mainMenu = document.getElementById('contextMenuMain'); const mainMenu = document.getElementById('contextMenuMain');
@ -442,6 +481,43 @@ export function getContextButtonScript(): string {
\`).join(''); \`).join('');
} }
// 渲染文档集列表
function renderDocumentSetList(data) {
const body = document.getElementById('contextMenuListBody');
if (!body) return;
filteredListData = data || [];
body.innerHTML = filteredListData.map((item, index) => \`
<div class="context-menu-list-item \${selectedItems.has(item.id) ? 'selected' : ''}" onclick="toggleDocumentSetSelection(\${index})">
<input type="checkbox" id="item-\${index}" \${selectedItems.has(item.id) ? 'checked' : ''} />
<label>\${item.name}</label>
</div>
\`).join('');
}
// 切换文档集选择
function toggleDocumentSetSelection(index) {
const selectedItem = filteredListData[index];
if (!selectedItem) return;
const selectedId = selectedItem.id;
const checkbox = document.getElementById('item-' + index);
const item = document.querySelectorAll('.context-menu-list-item')[index];
if (selectedItems.has(selectedId)) {
selectedItems.delete(selectedId);
if (checkbox) checkbox.checked = false;
if (item) item.classList.remove('selected');
} else {
selectedItems.add(selectedId);
if (checkbox) checkbox.checked = true;
if (item) item.classList.add('selected');
}
updateSelectedCount();
}
// 切换项选择 // 切换项选择
function toggleItemSelection(index) { function toggleItemSelection(index) {
const selectedItem = filteredListData[index]; const selectedItem = filteredListData[index];
@ -475,13 +551,28 @@ export function getContextButtonScript(): string {
// 确认选择 // 确认选择
function confirmSelection() { function confirmSelection() {
try { try {
if (currentListType === 'documentSetItem') {
const selected = currentListData.filter(item => selectedItems.has(item.id));
selected.forEach(docSet => {
(docSet.files || []).forEach(doc => {
addContextItem('docset', doc.absolutePath, doc.name || doc.absolutePath);
});
});
} else {
const selected = currentListData.filter(item => selectedItems.has(item.path)); const selected = currentListData.filter(item => selectedItems.has(item.path));
if (selected.length > 0) { if (selected.length > 0) {
if (currentListType === 'documentSet') {
vscode.postMessage({
command: 'saveDocumentSet',
documents: selected
});
} else {
selected.forEach(item => { selected.forEach(item => {
addContextItem(currentListType, item.path, item.relativePath || item.path); addContextItem(currentListType, item.path, item.relativePath || item.path);
}); });
} }
}
}
} finally { } finally {
const menu = document.getElementById('contextMenu'); const menu = document.getElementById('contextMenu');
const button = document.querySelector('.add-context-button'); const button = document.querySelector('.add-context-button');
@ -507,15 +598,24 @@ export function getContextButtonScript(): string {
toggleContextMenu(); toggleContextMenu();
} }
// 添加文档集
function handleAddDocumentSet() {
showDocumentSetList();
}
// 搜索功能 // 搜索功能
const searchInput = document.getElementById('contextMenuSearch'); const searchInput = document.getElementById('contextMenuSearch');
if (searchInput) { if (searchInput) {
searchInput.addEventListener('input', function(e) { searchInput.addEventListener('input', function(e) {
const keyword = (e.target.value || '').toLowerCase().trim(); const keyword = (e.target.value || '').toLowerCase().trim();
const filtered = currentListData.filter(item => const filtered = currentListData.filter(item =>
(item.relativePath || item.path || '').toLowerCase().includes(keyword) (item.name || item.relativePath || item.path || '').toLowerCase().includes(keyword)
); );
if (currentListType === 'documentSetItem') {
renderDocumentSetList(filtered);
} else {
renderList(filtered); renderList(filtered);
}
}); });
} }
@ -527,6 +627,10 @@ export function getContextButtonScript(): string {
switchToListView('选择文件', 'file', message.files); switchToListView('选择文件', 'file', message.files);
} else if (message.command === 'showWorkspaceFolderList') { } else if (message.command === 'showWorkspaceFolderList') {
switchToListView('选择文件夹', 'folder', message.folders); switchToListView('选择文件夹', 'folder', message.folders);
} else if (message.command === 'showWorkspaceDocumentSetList') {
switchToListView('选择文档', 'documentSet', message.files);
} else if (message.command === 'showDocumentSetList') {
showDocumentSetView(message.documents || []);
} }
}); });
`; `;

View File

@ -181,6 +181,7 @@ 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 'docset': icon = getDocumentIcon(); break;
case 'code': icon = getCodeIcon(); break; case 'code': icon = getCodeIcon(); break;
} }

View File

@ -0,0 +1,160 @@
/**
* 上下文设置组件
* 功能:管理文档集
*/
import {
getDocsetDialogContent,
getDocsetDialogStyles,
getDocsetDialogScript,
} from "./docsetDialog";
export function getContextSettingsComponentContent(): string {
return `
<div class="context-settings">
<div class="context-header">
<h3>上下文</h3>
</div>
<div class="context-section">
<div class="context-section-header">
<span>Docs</span>
<button class="context-add-btn" onclick="openAddDocumentSetDialog()">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="currentColor"/>
</svg>
添加文档集
</button>
</div>
<div class="context-docs-list" id="contextDocsList">
<div class="context-empty">
<p>暂无文档集</p>
</div>
</div>
</div>
</div>
${getDocsetDialogContent()}
`;
}
export function getContextSettingsComponentStyles(): string {
return `
.context-settings {
padding: 20px;
}
.context-header h3 {
margin: 0 0 20px 0;
font-size: 16px;
font-weight: 600;
}
.context-section {
background: transparent;
border: none;
padding: 0;
}
.context-section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--vscode-panel-border);
}
.context-section-header span {
font-size: 14px;
font-weight: 500;
}
.context-add-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
background: transparent;
color: var(--vscode-textLink-foreground);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
}
.context-add-btn:hover {
background: var(--vscode-toolbar-hoverBackground);
color: var(--vscode-textLink-activeForeground);
}
.context-add-btn svg {
width: 14px;
height: 14px;
}
.context-docs-list {
min-height: 100px;
}
.context-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
color: var(--vscode-descriptionForeground);
}
.context-empty p {
margin: 0 0 12px 0;
font-size: 13px;
}
.docset-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
margin-bottom: 8px;
background: var(--vscode-editor-background);
}
.docset-item:hover {
background: var(--vscode-list-hoverBackground);
}
.docset-name {
font-size: 13px;
font-weight: 500;
}
.docset-meta {
font-size: 11px;
color: var(--vscode-descriptionForeground);
flex: 1;
}
.docset-delete-btn {
background: transparent;
border: none;
color: var(--vscode-foreground);
cursor: pointer;
padding: 4px;
opacity: 0.6;
border-radius: 4px;
}
.docset-delete-btn:hover {
opacity: 1;
background: var(--vscode-toolbar-hoverBackground);
}
${getDocsetDialogStyles()}
`;
}
export function getContextSettingsComponentScript(): string {
return getDocsetDialogScript();
}

571
src/views/docsetDialog.ts Normal file
View File

@ -0,0 +1,571 @@
/**
* 文档集对话框组件
* 功能:添加文档集的对话框
*/
export function getDocsetDialogContent(): string {
return `
<div class="docset-dialog" id="docsetDialog">
<div class="docset-dialog-overlay" onclick="closeDocsetDialog()"></div>
<div class="docset-dialog-content">
<div class="docset-dialog-header">
<h3>添加文档集</h3>
<button onclick="closeDocsetDialog()">×</button>
</div>
<div class="docset-dialog-body">
<div class="docset-form-group">
<label>名称</label>
<input type="text" id="docsetName" placeholder="输入文档集名称" />
</div>
<div class="docset-form-group">
<label>文件</label>
<div class="docset-hint">支持 .md/.txt/.v/.sv/.pdf单个文件最大 10 MB文档集最大 50 MB最多 1000 个文件</div>
<button class="docset-add-file-btn" id="addFileBtn" onclick="addFileToDocset()">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="currentColor"/>
</svg>
添加文件
</button>
<div id="docsetFilesDisplay" style="display: none; margin-top: 8px;">
<div id="docsetFilesList" style="max-height: 200px; overflow-y: auto; border: 1px solid var(--vscode-input-border); border-radius: 4px; padding: 8px;"></div>
<div id="docsetFilesSummary" style="margin-top: 8px; font-size: 12px; color: var(--vscode-descriptionForeground);"></div>
</div>
</div>
</div>
<div class="docset-dialog-footer">
<button class="docset-btn-cancel" onclick="closeDocsetDialog()">取消</button>
<button class="docset-btn-confirm" onclick="confirmDocset()">确定</button>
</div>
</div>
</div>
<div class="delete-confirm-dialog" id="deleteConfirmDialog">
<div class="delete-confirm-content">
<div class="delete-confirm-title">确认删除</div>
<div class="delete-confirm-message" id="deleteConfirmMessage"></div>
<div class="delete-confirm-actions">
<button class="docset-btn-cancel" onclick="closeDeleteConfirm()">取消</button>
<button class="docset-btn-confirm" onclick="confirmDelete()">确定</button>
</div>
</div>
</div>
<div class="rename-dialog" id="renameDialog">
<div class="rename-content">
<div class="rename-title">修改名称</div>
<input type="text" id="renameInput" class="rename-input" placeholder="输入新名称" />
<div class="rename-actions">
<button class="docset-btn-cancel" onclick="closeRenameDialog()">取消</button>
<button class="docset-btn-confirm" onclick="confirmRename()">确定</button>
</div>
</div>
</div>
`;
}
export function getDocsetDialogStyles(): string {
return `
.docset-dialog {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000;
}
.docset-dialog.active {
display: flex;
align-items: center;
justify-content: center;
}
.docset-dialog-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.docset-dialog-content {
position: relative;
width: 90%;
max-width: 500px;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
display: flex;
flex-direction: column;
max-height: 80vh;
}
.docset-dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--vscode-panel-border);
}
.docset-dialog-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.docset-dialog-header button {
width: 32px;
height: 32px;
background: transparent;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--vscode-foreground);
border-radius: 4px;
}
.docset-dialog-header button:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.docset-dialog-body {
padding: 20px;
overflow-y: auto;
}
.docset-form-group {
margin-bottom: 20px;
}
.docset-form-group label {
display: block;
margin-bottom: 8px;
font-size: 13px;
font-weight: 500;
}
.docset-hint {
font-size: 11px;
font-weight: 400;
color: var(--vscode-descriptionForeground);
margin-bottom: 8px;
}
.docset-form-group input {
width: 100%;
padding: 8px 12px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
color: var(--vscode-input-foreground);
font-size: 13px;
box-sizing: border-box;
}
.docset-add-file-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
background: transparent;
color: var(--vscode-textLink-foreground);
border: 1px solid var(--vscode-textLink-foreground);
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.docset-add-file-btn:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.docset-add-file-btn svg {
width: 14px;
height: 14px;
}
.docset-dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 16px 20px;
border-top: 1px solid var(--vscode-panel-border);
}
.docset-dialog-footer button {
padding: 6px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
.docset-btn-cancel {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
}
.docset-btn-cancel:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.docset-btn-confirm {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.docset-btn-confirm:hover {
background: var(--vscode-button-hoverBackground);
}
.docset-delete-btn, .docset-change-btn {
position: relative;
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
color: var(--vscode-foreground);
opacity: 0.6;
}
.docset-delete-btn:hover, .docset-change-btn:hover {
opacity: 1;
}
.docset-delete-btn:hover::after, .docset-change-btn:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: var(--vscode-editorHoverWidget-background);
color: var(--vscode-editorHoverWidget-foreground);
border: 1px solid var(--vscode-editorHoverWidget-border);
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
white-space: nowrap;
margin-bottom: 4px;
z-index: 1000;
}
.delete-confirm-dialog {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10000;
align-items: center;
justify-content: center;
}
.delete-confirm-dialog.active {
display: flex;
}
.delete-confirm-content {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 20px;
min-width: 300px;
max-width: 400px;
}
.delete-confirm-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.delete-confirm-message {
font-size: 13px;
color: var(--vscode-descriptionForeground);
margin-bottom: 16px;
}
.delete-confirm-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.delete-confirm-actions button {
padding: 6px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
.rename-dialog {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10000;
align-items: center;
justify-content: center;
}
.rename-dialog.active {
display: flex;
}
.rename-content {
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 20px;
min-width: 300px;
max-width: 400px;
}
.rename-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.rename-input {
width: 100%;
padding: 8px 12px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
color: var(--vscode-input-foreground);
font-size: 13px;
box-sizing: border-box;
margin-bottom: 16px;
}
.rename-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.rename-actions button {
padding: 6px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
`;
}
export function getDocsetDialogScript(): string {
return `
let docsetFiles = [];
function openAddDocumentSetDialog() {
const dialog = document.getElementById('docsetDialog');
if (dialog) {
dialog.classList.add('active');
docsetFiles = [];
document.getElementById('docsetName').value = '';
document.getElementById('addFileBtn').style.display = 'flex';
document.getElementById('docsetFilesDisplay').style.display = 'none';
}
}
function closeDocsetDialog() {
const dialog = document.getElementById('docsetDialog');
if (dialog) {
dialog.classList.remove('active');
}
}
function addFileToDocset() {
vscode.postMessage({ command: 'selectFilesForDocset' });
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function updateDocsetDisplay() {
if (docsetFiles.length === 0) {
document.getElementById('addFileBtn').style.display = 'flex';
document.getElementById('docsetFilesDisplay').style.display = 'none';
return;
}
document.getElementById('addFileBtn').style.display = 'none';
document.getElementById('docsetFilesDisplay').style.display = 'block';
const listEl = document.getElementById('docsetFilesList');
const summaryEl = document.getElementById('docsetFilesSummary');
listEl.innerHTML = docsetFiles.map((file, index) => \`
<div style="display: flex; justify-content: space-between; align-items: center; font-size: 12px; padding: 4px 0; color: var(--vscode-foreground);">
<span>\${file.name || file.absolutePath}</span>
<button onclick="removeDocsetFile(\${index})" style="background: transparent; border: none; color: var(--vscode-foreground); cursor: pointer; padding: 0 4px; opacity: 0.7;">
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
<path d="M10 3h3v1h-1v9l-1 1H4l-1-1V4H2V3h3V2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zM9 2H6v1h3V2zM4 13h7V4H4v9zm2-8H5v7h1V5zm1 0h1v7H7V5zm2 0h1v7H9V5z"/>
</svg>
</button>
</div>
\`).join('');
const totalSize = docsetFiles.reduce((sum, f) => sum + (f.size || 0), 0);
summaryEl.textContent = \`已选择 \${docsetFiles.length} 个文件,总大小 \${formatFileSize(totalSize)}\`;
}
function removeDocsetFile(index) {
docsetFiles.splice(index, 1);
updateDocsetDisplay();
}
function confirmDocset() {
const name = document.getElementById('docsetName').value.trim();
if (!name) {
alert('请输入文档集名称');
return;
}
if (docsetFiles.length === 0) {
alert('请添加至少一个文件');
return;
}
vscode.postMessage({
command: 'saveDocumentSet',
name: name,
documents: docsetFiles
});
closeDocsetDialog();
}
function renderDocumentSets(documentSets) {
const listEl = document.getElementById('contextDocsList');
if (!listEl) return;
if (!documentSets || documentSets.length === 0) {
listEl.innerHTML = '<div class="context-empty"><p>暂无文档集</p></div>';
return;
}
listEl.innerHTML = documentSets.map(ds => \`
<div class="docset-item">
<div class="docset-name">\${ds.name}</div>
<div class="docset-meta">更新于 \${new Date(ds.updatedAt).toLocaleString('zh-CN')}</div>
<button class="docset-change-btn" data-tooltip="修改名称" onclick="changeDocsetName('\${ds.id}', '\${ds.name}')">
<svg t="1773883957219" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7170" width="14" height="14">
<path d="M745.76 369.86l-451 537.48a18.693 18.693 0 0 1-8.46 5.74l-136.58 45.27c-13.24 4.39-26.46-6.71-24.43-20.5l20.86-142.36c0.5-3.44 1.95-6.67 4.19-9.33l451-537.48c6.65-7.93 18.47-8.96 26.4-2.31l115.71 97.1c7.92 6.64 8.96 18.46 2.31 26.39zM894.53 192.56l-65.9 78.53c-6.65 7.93-18.47 8.96-26.4 2.31l-115.71-97.1c-7.93-6.65-8.96-18.47-2.31-26.4l65.9-78.53c6.65-7.93 18.47-8.96 26.4-2.31l115.71 97.1c7.93 6.65 8.96 18.47 2.31 26.4z" fill="currentColor" p-id="7171"></path>
</svg>
</button>
<button class="docset-delete-btn" data-tooltip="删除" onclick="showDeleteConfirm('\${ds.id}', '\${ds.name}')">
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
<path d="M10 3h3v1h-1v9l-1 1H4l-1-1V4H2V3h3V2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zM9 2H6v1h3V2zM4 13h7V4H4v9zm2-8H5v7h1V5zm1 0h1v7H7V5zm2 0h1v7H9V5z"/>
</svg>
</button>
</div>
\`).join('');
}
let deleteTargetId = null;
let renameTargetId = null;
let renameOriginalName = null;
window.showDeleteConfirm = function(id, name) {
deleteTargetId = id;
document.getElementById('deleteConfirmMessage').textContent = \`确定要删除文档集 "\${name}" 吗?此操作不可恢复。\`;
document.getElementById('deleteConfirmDialog').classList.add('active');
};
window.changeDocsetName = function(id, name) {
renameTargetId = id;
renameOriginalName = name;
document.getElementById('renameInput').value = name;
document.getElementById('renameDialog').classList.add('active');
setTimeout(() => document.getElementById('renameInput').focus(), 100);
};
window.closeDeleteConfirm = function() {
document.getElementById('deleteConfirmDialog').classList.remove('active');
deleteTargetId = null;
};
window.closeRenameDialog = function() {
document.getElementById('renameDialog').classList.remove('active');
renameTargetId = null;
renameOriginalName = null;
};
window.confirmDelete = function() {
if (deleteTargetId) {
vscode.postMessage({ command: 'deleteDocumentSet', id: deleteTargetId });
closeDeleteConfirm();
}
};
window.confirmRename = function() {
const newName = document.getElementById('renameInput').value.trim();
if (!newName) {
alert('请输入名称');
return;
}
if (newName !== renameOriginalName) {
vscode.postMessage({ command: 'changeDocumentSetName', id: renameTargetId, newName: newName });
}
closeRenameDialog();
};
window.addEventListener('message', event => {
const message = event.data;
if (message.command === 'filesSelectedForDocset') {
if (message.errors && message.errors.length > 0) {
alert('部分文件添加失败:\\n' + message.errors.join('\\n'));
}
const MAX_FILE_SIZE = 10 * 1024 * 1024;
const MAX_TOTAL_SIZE = 50 * 1024 * 1024;
const MAX_FILE_COUNT = 1000;
const vaildFiles = [];
const errors = [];
for (const file of message.files) {
if (file.size > MAX_FILE_SIZE) {
errors.push(\`\${file.name || file.absolutePath} 超过单个文件大小限制(\${formatFileSize(MAX_FILE_SIZE)}\`);
} else {
vaildFiles.push(file);
}
}
const newFiles = [...docsetFiles, ...vaildFiles];
const totalSize = newFiles.reduce((sum, f) => sum + (f.size || 0), 0);
if (newFiles.length > MAX_FILE_COUNT) {
errors.push(\`文档数量超过限制最多1000个当前数量\${newFiles.length} 个)\`);
return;
}
if (totalSize > MAX_TOTAL_SIZE) {
errors.push(\`文档集总大小超过 50MB 限制,当前大小为(\${formatFileSize(MAX_TOTAL_SIZE)}\`);
return;
}
if(errors.length > 0) {
alert('以下文件被跳过:\\n' + errors.join('\\n'));
}
docsetFiles = newFiles;
updateDocsetDisplay();
} else if (message.command === 'documentSetSaved') {
renderDocumentSets(message.documentSets);
}
});
`;
}

View File

@ -553,7 +553,7 @@ export function getMessageAreaStyles(): string {
} }
.segment-question .question-option { .segment-question .question-option {
padding: 8px 16px; padding: 8px 16px;
background: #3d3f41; background: var(--vscode-textBlockQuote-background);
color: var(--vscode-foreground); color: var(--vscode-foreground);
border: 1px solid #474747; border: 1px solid #474747;
border-radius: 6px; border-radius: 6px;
@ -563,13 +563,13 @@ export function getMessageAreaStyles(): string {
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
.segment-question .question-option:hover { .segment-question .question-option:hover {
background: #005a9e; background: var(--vscode-button-hoverBackground);
border-color: #005a9e; border-color: var(--vscode-button-hoverBackground);
} }
.segment-question .question-option.selected { .segment-question .question-option.selected {
background: #007ACC; background: var(--vscode-button-background);
color: #ffffff; color: var(--vscode-button-foreground);
border-color: #007ACC; border-color: var(--vscode-button-background);
} }
.segment-question.answered .question-option:not(.selected) { .segment-question.answered .question-option:not(.selected) {
opacity: 0.5; opacity: 0.5;

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,169 @@ 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,
};
// 发送消息到扩展 // 显示添加规则弹窗
function showAddRuleModal() {
editingRule = null;
document.getElementById('modalTitle').textContent = '创建个人规则';
document.getElementById('ruleNameInput').value = '';
document.getElementById('ruleTextarea').value = '';
document.getElementById('ruleModal').style.display = 'flex';
}
// 关闭弹窗
function closeRuleModal() {
document.getElementById('ruleModal').style.display = 'none';
closeAllDropdowns();
}
// 切换下拉菜单
function toggleDropdown(filename, event) {
event.stopPropagation();
closeAllDropdowns();
const dropdown = document.getElementById('dropdown-' + filename);
if (dropdown) {
dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
}
}
// 关闭所有下拉菜单
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({ vscode.postMessage({
command: 'saveRulesSettings', command: 'updatePersonalRule',
settings: settings 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);
}
}
//监听启用个人规则开关变化
document.getElementById('enablePersonalRulesCheckbox').addEventListener('change', function() {
const enabled = this.checked;
vscode.postMessage({ command: 'updatePersonalRulesEnabled', enabled: enabled });
}); });
// 显示保存成功提示 // 页面加载时请求规则数据
console.log('规则设置已保存', settings); vscode.postMessage({ command: 'loadPersonalRules' });
}
// 重置规则设置
function resetRulesSettings() {
document.getElementById('enableCustomRulesCheckbox').checked = true;
document.getElementById('systemRulesTextarea').value = '';
document.getElementById('codeRulesTextarea').value = '';
document.getElementById('verilogRulesTextarea').value = '';
console.log('规则设置已重置为默认值');
}
// 加载规则设置
function loadRulesSettings(settings) {
if (!settings) return;
if (settings.enableCustomRules !== undefined) {
document.getElementById('enableCustomRulesCheckbox').checked = settings.enableCustomRules;
}
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;
}
}
`; `;
} }

View File

@ -8,6 +8,11 @@ import {
getRulesSettingsComponentStyles, getRulesSettingsComponentStyles,
getRulesSettingsComponentScript, getRulesSettingsComponentScript,
} from "./rulesSettingsComponent"; } from "./rulesSettingsComponent";
import {
getContextSettingsComponentContent,
getContextSettingsComponentStyles,
getContextSettingsComponentScript,
} from "./contextSettingsComponent";
/** /**
* 获取设置面板的 HTML 内容 * 获取设置面板的 HTML 内容
@ -34,6 +39,9 @@ export function getSettingsComponentContent(): string {
<button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')"> <button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')">
规则 规则
</button> </button>
<button class="settings-nav-item" data-tab="context" onclick="switchSettingsTab('context')">
上下文
</button>
</div> </div>
<div class="settings-content"> <div class="settings-content">
@ -43,6 +51,9 @@ export function getSettingsComponentContent(): string {
<div class="settings-tab-content" id="rulesSettings"> <div class="settings-tab-content" id="rulesSettings">
${getRulesSettingsComponentContent()} ${getRulesSettingsComponentContent()}
</div> </div>
<div class="settings-tab-content" id="contextSettings">
${getContextSettingsComponentContent()}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -187,6 +198,7 @@ export function getSettingsComponentStyles(): string {
${getGeneralSettingsComponentStyles()} ${getGeneralSettingsComponentStyles()}
${getRulesSettingsComponentStyles()} ${getRulesSettingsComponentStyles()}
${getContextSettingsComponentStyles()}
`; `;
} }
@ -197,6 +209,7 @@ export function getSettingsComponentScript(): string {
return ` return `
${getGeneralSettingsComponentScript()} ${getGeneralSettingsComponentScript()}
${getRulesSettingsComponentScript()} ${getRulesSettingsComponentScript()}
${getContextSettingsComponentScript()}
// 打开设置面板 // 打开设置面板
function openSettingsModal() { function openSettingsModal() {
@ -235,7 +248,21 @@ export function getSettingsComponentScript(): string {
content.classList.remove('active'); content.classList.remove('active');
} }
}); });
// 切换到上下文标签页时加载文档集列表
if (tabName === 'context') {
vscode.postMessage({ command: 'loadDocumentSets' });
} }
}
// 监听打开设置标签页的消息
window.addEventListener('message', event => {
const message = event.data;
if (message.command === 'openSettingsTab') {
openSettingsModal();
switchSettingsTab(message.tab);
}
});
// 阻止点击模态框内容时关闭 // 阻止点击模态框内容时关闭
document.addEventListener('click', (event) => { document.addEventListener('click', (event) => {

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);