feat: 添加个人规则功能
- 新增个人规则管理模块 (personalRulesManager.ts) - 支持创建、编辑、删除多条规则 - 规则存储在用户目录 ~/.iccoder/rules/ - 对话时自动将规则传递给后端 - 添加后端对接文档和 webpack 优化指南
This commit is contained in:
247
docs/personal-rules-backend-integration.md
Normal file
247
docs/personal-rules-backend-integration.md
Normal 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
|
||||
379
docs/webpack-optimization-guide.md
Normal file
379
docs/webpack-optimization-guide.md
Normal file
@ -0,0 +1,379 @@
|
||||
# Webpack 打包优化完整教程
|
||||
|
||||
## 目录
|
||||
1. [优化前的问题](#优化前的问题)
|
||||
2. [优化方案详解](#优化方案详解)
|
||||
3. [配置对比](#配置对比)
|
||||
4. [使用指南](#使用指南)
|
||||
5. [效果验证](#效果验证)
|
||||
|
||||
---
|
||||
|
||||
## 优化前的问题
|
||||
|
||||
### 原始配置存在的问题
|
||||
|
||||
```javascript
|
||||
// ❌ 问题1:固定使用 none 模式
|
||||
mode: 'none'
|
||||
// 导致:生产环境代码不压缩,体积大
|
||||
|
||||
// ❌ 问题2:没有 Tree Shaking
|
||||
// 导致:未使用的代码也被打包
|
||||
|
||||
// ❌ 问题3:ts-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. 发布前用生产模式打包
|
||||
@ -117,6 +117,11 @@
|
||||
"configuration": {
|
||||
"title": "IC Coder",
|
||||
"properties": {
|
||||
"ic-coder.personalRulesEnabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "启用个人规则"
|
||||
},
|
||||
"ic-coder.enableSystemNotification": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
||||
@ -200,3 +200,8 @@ export const setting = `<svg t="1768535209135" class="icon" viewBox="0 0 1024 10
|
||||
* 成功的图标svg
|
||||
*/
|
||||
export const successIconSvg = `<svg t="1771828214449" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5360" width="16" height="16"><path d="M748.864 302.528a49.024 49.024 0 0 1 68.288-1.28 46.656 46.656 0 0 1 5.952 61.44l-4.608 5.44-346.56 353.344a49.024 49.024 0 0 1-64.384 4.608l-5.504-4.864-196.8-203.264a46.72 46.72 0 0 1 1.792-66.88A49.024 49.024 0 0 1 270.08 448l5.312 4.736 162.048 167.232L748.8 302.528z" fill="#8a8a8a" p-id="5361"></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>`;
|
||||
|
||||
@ -25,6 +25,7 @@ import { MessageType } from "../types/chatHistory";
|
||||
import { getCachedUserInfo } from "../services/userService";
|
||||
import { isTokenExpired } from "../utils/jwtUtils";
|
||||
import { setBalanceUpdateCallback } from "../services/creditsService";
|
||||
import { savePersonalRule, updatePersonalRule, deletePersonalRule, loadPersonalRules } from "../utils/personalRulesManager";
|
||||
|
||||
/**
|
||||
* 获取会员等级图标 URI
|
||||
@ -494,6 +495,53 @@ export async function showICHelperPanel(
|
||||
// 退出登录
|
||||
vscode.commands.executeCommand("ic-coder.logout");
|
||||
break;
|
||||
case "savePersonalRule":
|
||||
// 保存个人规则
|
||||
if (message.name && message.content && message.enabled !== undefined) {
|
||||
const success = await savePersonalRule(message.name, message.content, message.enabled);
|
||||
if (success) {
|
||||
const rulesData = loadPersonalRules();
|
||||
panel.webview.postMessage({
|
||||
command: "personalRulesLoaded",
|
||||
data: rulesData
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "updatePersonalRule":
|
||||
// 更新个人规则
|
||||
if (message.filename && message.name && message.content && message.enabled !== undefined) {
|
||||
const success = await updatePersonalRule(message.filename, message.name, message.content, message.enabled);
|
||||
if (success) {
|
||||
const rulesData = loadPersonalRules();
|
||||
panel.webview.postMessage({
|
||||
command: "personalRulesLoaded",
|
||||
data: rulesData
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "deletePersonalRule":
|
||||
// 删除个人规则
|
||||
if (message.filename) {
|
||||
const success = await deletePersonalRule(message.filename);
|
||||
if (success) {
|
||||
const rulesData = loadPersonalRules();
|
||||
panel.webview.postMessage({
|
||||
command: "personalRulesLoaded",
|
||||
data: rulesData
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "loadPersonalRules":
|
||||
// 加载个人规则
|
||||
const rulesData = loadPersonalRules();
|
||||
panel.webview.postMessage({
|
||||
command: "personalRulesLoaded",
|
||||
data: rulesData
|
||||
});
|
||||
break;
|
||||
case "openFile":
|
||||
// 打开文件
|
||||
if (message.filePath) {
|
||||
|
||||
@ -28,6 +28,7 @@ import type {
|
||||
PlanConfirmEvent,
|
||||
} from "../types/api";
|
||||
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
|
||||
import { getActiveRules } from "../utils/personalRulesManager";
|
||||
import { ChatHistoryManager } from "../utils/chatHistoryManager";
|
||||
import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils";
|
||||
import { updateCachedBalance } from "./creditsService";
|
||||
@ -502,6 +503,7 @@ export class DialogSession {
|
||||
compactedData: compactedData || undefined,
|
||||
newMessages: newMessages.length > 0 ? newMessages : undefined,
|
||||
knowledgeData: knowledgeData || undefined,
|
||||
personalRules: getActiveRules() || undefined,
|
||||
};
|
||||
|
||||
// 追踪用户消息
|
||||
|
||||
@ -48,6 +48,8 @@ export interface DialogRequest {
|
||||
newMessages?: CompactedMessage[];
|
||||
/** 知识图谱数据(JSON 字符串,用于恢复知识图谱) */
|
||||
knowledgeData?: string;
|
||||
/** 个人规则 */
|
||||
personalRules?: string;
|
||||
}
|
||||
|
||||
// ============== SSE 事件类型 ==============
|
||||
|
||||
147
src/utils/personalRulesManager.ts
Normal file
147
src/utils/personalRulesManager.ts
Normal 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');
|
||||
}
|
||||
@ -1,77 +1,55 @@
|
||||
import { peopleRules } from "../constants/toolIcons";
|
||||
|
||||
/**
|
||||
* 获取规则设置组件的 HTML 内容
|
||||
*/
|
||||
export function getRulesSettingsComponentContent(): string {
|
||||
return `
|
||||
<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-item">
|
||||
<div class="settings-item-header">
|
||||
<label class="settings-item-label">启用自定义规则</label>
|
||||
<span class="settings-item-description">使用自定义规则来控制 AI 行为</span>
|
||||
<label class="settings-item-label">启用个人规则</label>
|
||||
<span class="settings-item-description">规则将在每次对话时自动应用</span>
|
||||
</div>
|
||||
<label class="settings-switch">
|
||||
<input type="checkbox" id="enableCustomRulesCheckbox" checked>
|
||||
<input type="checkbox" id="enablePersonalRulesCheckbox" checked>
|
||||
<span class="settings-switch-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h4 class="settings-subsection-title">系统规则</h4>
|
||||
<div class="rules-textarea-container">
|
||||
<textarea
|
||||
class="rules-textarea"
|
||||
id="systemRulesTextarea"
|
||||
placeholder="在此输入系统规则,例如: - 始终使用中文回复 - 代码注释要详细 - 遵循项目编码规范"
|
||||
rows="8"
|
||||
></textarea>
|
||||
<div class="rules-textarea-hint">
|
||||
系统规则会在每次对话开始时应用
|
||||
</div>
|
||||
</div>
|
||||
<div class="rules-list" id="rulesList">
|
||||
<!-- 规则列表将动态插入这里 -->
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h4 class="settings-subsection-title">代码生成规则</h4>
|
||||
<div class="rules-textarea-container">
|
||||
<!-- 添加/编辑规则弹窗 -->
|
||||
<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
|
||||
class="rules-textarea"
|
||||
id="codeRulesTextarea"
|
||||
placeholder="在此输入代码生成规则,例如: - 使用 TypeScript 严格模式 - 函数命名使用驼峰命名法 - 添加必要的错误处理"
|
||||
rows="8"
|
||||
class="rule-textarea"
|
||||
id="ruleTextarea"
|
||||
placeholder="输入规则内容..."
|
||||
rows="10"
|
||||
></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 class="settings-section">
|
||||
<h4 class="settings-subsection-title">Verilog 规则</h4>
|
||||
<div class="rules-textarea-container">
|
||||
<textarea
|
||||
class="rules-textarea"
|
||||
id="verilogRulesTextarea"
|
||||
placeholder="在此输入 Verilog 代码规则,例如: - 使用非阻塞赋值 (<=) 在时序逻辑中 - 模块命名使用小写加下划线 - 添加详细的端口注释"
|
||||
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>
|
||||
`;
|
||||
}
|
||||
@ -85,11 +63,144 @@ export function getRulesSettingsComponentStyles(): string {
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.rules-textarea-container {
|
||||
margin-top: 8px;
|
||||
.rules-header {
|
||||
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%;
|
||||
padding: 12px;
|
||||
background: var(--vscode-input-background);
|
||||
@ -98,26 +209,20 @@ export function getRulesSettingsComponentStyles(): string {
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rules-textarea:focus {
|
||||
.rule-textarea:focus {
|
||||
border-color: var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.rules-textarea::placeholder {
|
||||
color: var(--vscode-input-placeholderForeground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.rules-textarea-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-style: italic;
|
||||
.rule-modal-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
}
|
||||
@ -127,51 +232,143 @@ export function getRulesSettingsComponentStyles(): string {
|
||||
*/
|
||||
export function getRulesSettingsComponentScript(): string {
|
||||
return `
|
||||
// 保存规则设置
|
||||
function saveRulesSettings() {
|
||||
const settings = {
|
||||
enableCustomRules: document.getElementById('enableCustomRulesCheckbox').checked,
|
||||
systemRules: document.getElementById('systemRulesTextarea').value,
|
||||
codeRules: document.getElementById('codeRulesTextarea').value,
|
||||
verilogRules: document.getElementById('verilogRulesTextarea').value,
|
||||
};
|
||||
let currentRules = [];
|
||||
let editingRule = null;
|
||||
|
||||
// 发送消息到扩展
|
||||
// 显示添加规则弹窗
|
||||
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({
|
||||
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();
|
||||
vscode.postMessage({
|
||||
command: 'saveRulesSettings',
|
||||
settings: settings
|
||||
command: 'deletePersonalRule',
|
||||
filename: filename
|
||||
});
|
||||
|
||||
// 显示保存成功提示
|
||||
console.log('规则设置已保存', settings);
|
||||
}
|
||||
|
||||
// 重置规则设置
|
||||
function resetRulesSettings() {
|
||||
document.getElementById('enableCustomRulesCheckbox').checked = true;
|
||||
document.getElementById('systemRulesTextarea').value = '';
|
||||
document.getElementById('codeRulesTextarea').value = '';
|
||||
document.getElementById('verilogRulesTextarea').value = '';
|
||||
// 渲染规则列表
|
||||
function renderRulesList(rules) {
|
||||
currentRules = rules || [];
|
||||
const listEl = document.getElementById('rulesList');
|
||||
|
||||
console.log('规则设置已重置为默认值');
|
||||
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 loadRulesSettings(settings) {
|
||||
if (!settings) return;
|
||||
|
||||
if (settings.enableCustomRules !== undefined) {
|
||||
document.getElementById('enableCustomRulesCheckbox').checked = settings.enableCustomRules;
|
||||
// 加载规则列表
|
||||
function loadPersonalRules(data) {
|
||||
if (data && data.enabled !== undefined) {
|
||||
document.getElementById('enablePersonalRulesCheckbox').checked = data.enabled;
|
||||
}
|
||||
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;
|
||||
if (data && data.rules) {
|
||||
renderRulesList(data.rules);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时请求规则数据
|
||||
vscode.postMessage({ command: 'loadPersonalRules' });
|
||||
`;
|
||||
}
|
||||
|
||||
@ -715,6 +715,13 @@ export function getWebviewContent(
|
||||
}
|
||||
break;
|
||||
|
||||
case 'personalRulesLoaded':
|
||||
// 加载个人规则数据
|
||||
if (typeof loadPersonalRules === 'function') {
|
||||
loadPersonalRules(message.data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'autoSendMessage':
|
||||
// 自动发送待发送的消息(登录后)
|
||||
console.log('[WebView] 自动发送待发送消息:', message.text);
|
||||
|
||||
Reference in New Issue
Block a user