feat: 添加个人规则功能

- 新增个人规则管理模块 (personalRulesManager.ts)
   - 支持创建、编辑、删除多条规则
   - 规则存储在用户目录 ~/.iccoder/rules/
   - 对话时自动将规则传递给后端
   - 添加后端对接文档和 webpack 优化指南
This commit is contained in:
Roe-xin
2026-03-07 15:13:54 +08:00
parent 06573e37d7
commit 8751944053
10 changed files with 1139 additions and 100 deletions

View File

@ -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="在此输入系统规则,例如:&#10;- 始终使用中文回复&#10;- 代码注释要详细&#10;- 遵循项目编码规范"
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="在此输入代码生成规则,例如:&#10;- 使用 TypeScript 严格模式&#10;- 函数命名使用驼峰命名法&#10;- 添加必要的错误处理"
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 代码规则,例如:&#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>
`;
}
@ -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' });
`;
}