Merge branch 'feat/codeToChat' into feat/Knowledge-Base

This commit is contained in:
Roe-xin
2026-03-17 17:28:53 +08:00
11 changed files with 1234 additions and 103 deletions

View File

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

View File

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

View File

@ -28,6 +28,7 @@ import type {
PlanConfirmEvent,
} 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,
};
// 追踪用户消息

View File

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

View File

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

View File

@ -1,77 +1,67 @@
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">
<div class="rules-list" id="rulesList">
<!-- 规则列表将动态插入这里 -->
</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
class="rules-textarea"
id="systemRulesTextarea"
placeholder="在此输入系统规则,例如:&#10;- 始终使用中文回复&#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">代码生成规则</h4>
<div class="rules-textarea-container">
<textarea
class="rules-textarea"
id="codeRulesTextarea"
placeholder="在此输入代码生成规则,例如:&#10;- 使用 TypeScript 严格模式&#10;- 函数命名使用驼峰命名法&#10;- 添加必要的错误处理"
rows="8"
></textarea>
<div class="rules-textarea-hint">
这些规则会在生成代码时应用
<!-- 删除确认弹窗 -->
<div class="rule-modal" id="deleteConfirmModal" style="display: none;">
<div class="rule-modal-content" style="width: 400px;">
<h4>确认删除</h4>
<p id="deleteConfirmText" style="color: var(--vscode-foreground); margin: 16px 0;"></p>
<div class="rule-modal-actions">
<button class="settings-button settings-button-primary" onclick="confirmDelete()">确定</button>
<button class="settings-button settings-button-secondary" onclick="closeDeleteConfirmModal()">取消</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 +75,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 +221,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 +244,163 @@ 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;
let deletingFilename = null;
// 发送消息到扩展
vscode.postMessage({
command: 'saveRulesSettings',
settings: settings
});
// 显示保存成功提示
console.log('规则设置已保存', settings);
// 显示添加规则弹窗
function showAddRuleModal() {
editingRule = null;
document.getElementById('modalTitle').textContent = '创建个人规则';
document.getElementById('ruleNameInput').value = '';
document.getElementById('ruleTextarea').value = '';
document.getElementById('ruleModal').style.display = 'flex';
}
// 重置规则设置
function resetRulesSettings() {
document.getElementById('enableCustomRulesCheckbox').checked = true;
document.getElementById('systemRulesTextarea').value = '';
document.getElementById('codeRulesTextarea').value = '';
document.getElementById('verilogRulesTextarea').value = '';
console.log('规则设置已重置为默认值');
// 关闭弹窗
function closeRuleModal() {
document.getElementById('ruleModal').style.display = 'none';
closeAllDropdowns();
}
// 加载规则设置
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;
// 切换下拉菜单
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();
const rule = currentRules.find(r => r.filename === filename);
const ruleName = rule ? rule.name : filename;
deletingFilename = filename;
document.getElementById('deleteConfirmText').textContent = '确定要删除规则"' + ruleName + '"吗?此操作无法撤销。';
document.getElementById('deleteConfirmModal').style.display = 'flex';
}
// 关闭删除确认弹窗
function closeDeleteConfirmModal() {
document.getElementById('deleteConfirmModal').style.display = 'none';
deletingFilename = null;
}
// 确认删除
function confirmDelete() {
if (deletingFilename) {
vscode.postMessage({
command: 'deletePersonalRule',
filename: deletingFilename
});
}
closeDeleteConfirmModal();
}
// 渲染规则列表
function renderRulesList(rules) {
currentRules = rules || [];
const listEl = document.getElementById('rulesList');
if (currentRules.length === 0) {
listEl.innerHTML = '<div style="color: var(--vscode-descriptionForeground); padding: 16px; text-align: center;">暂无规则,点击"+ 创建"添加</div>';
return;
}
const peopleRulesIcon = '${peopleRules}';
listEl.innerHTML = currentRules.map(rule => \`
<div class="rule-item">
<div style="display: flex; align-items: center; gap: 8px;">
\${peopleRulesIcon}
<div class="rule-item-name">\${rule.filename}</div>
</div>
<div class="rule-item-menu">
<svg class="rule-menu-icon" onclick="toggleDropdown('\${rule.filename}', event)" viewBox="0 0 16 16" fill="currentColor">
<circle cx="8" cy="3" r="1.5"/>
<circle cx="8" cy="8" r="1.5"/>
<circle cx="8" cy="13" r="1.5"/>
</svg>
<div class="rule-dropdown" id="dropdown-\${rule.filename}" style="display: none;">
<button onclick="editRule('\${rule.filename}')">编辑</button>
<button onclick="deleteRule('\${rule.filename}')">删除</button>
</div>
</div>
</div>
\`).join('');
}
// 加载规则列表
function loadPersonalRules(data) {
if (data && data.enabled !== undefined) {
document.getElementById('enablePersonalRulesCheckbox').checked = data.enabled;
}
if (data && data.rules) {
renderRulesList(data.rules);
}
}
// 页面加载时请求规则数据
vscode.postMessage({ command: 'loadPersonalRules' });
`;
}

View File

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