v0.13.0: 记忆系统重构 — MEMORY.md 文件化 + 路径保护 + 自动提取优化
核心变更: - 删除 SQLite memories 表 + FTS5 + IVF 向量存储引擎 (~1300行) - 4 个记忆工具合并为 1 个 memory 工具 (5 action) - 记忆存储改为工作空间 MEMORY.md 单文件,严格格式校验 - 路径保护: checkPathAllowed 拦截所有工具,仅 memory 专用 IPC 通道可访问 - 应用启动/工作空间切换时自动校验并初始化 MEMORY.md(格式错误自动备份重建) - 自动记忆提取重建: 对话结束时触发,多层质量过滤(内容/泛化/去重/安全/importance门槛) - 工具总数: 42→40,UI 全面更新(帮助面板、工具面板、设置面板、README) - 版本号更新: 0.12.11 → 0.13.0
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
import { state, KEYS } from '../state/state.js';
|
||||
import { debounce } from '../utils/utils.js';
|
||||
import { encryptData, decryptData } from '../services/crypto.js';
|
||||
import { setMemoryEnabled, setEmbeddingModel, getEmbeddingModel, getMemoryCache } from '../services/memory-manager.js';
|
||||
import { logSetting, logInfo, logWarn, logError, logSuccess } from '../services/log-service.js';
|
||||
import { checkConnection, updateConnectionInfo, updateRunningModels } from './header.js';
|
||||
import { loadModels } from './model-bar.js';
|
||||
@@ -224,33 +223,6 @@ export function initSettingsModal(): void {
|
||||
});
|
||||
|
||||
|
||||
// ── Agent 记忆设置 ──
|
||||
const toggleMemory = document.querySelector('#toggleMemoryEnabled') as HTMLInputElement;
|
||||
if (toggleMemory) {
|
||||
toggleMemory.addEventListener('change', () => {
|
||||
setMemoryEnabled(toggleMemory.checked);
|
||||
showToast(toggleMemory.checked ? 'Agent 记忆已开启' : 'Agent 记忆已关闭', 'info');
|
||||
logSetting('Agent 记忆', toggleMemory.checked ? '开启' : '关闭');
|
||||
});
|
||||
}
|
||||
|
||||
// ── 向量记忆引擎设置 ──
|
||||
const selectEmbedModel = document.querySelector('#selectMemoryEmbedModel') as HTMLSelectElement;
|
||||
if (selectEmbedModel) {
|
||||
selectEmbedModel.addEventListener('change', async () => {
|
||||
const model = selectEmbedModel.value;
|
||||
await setEmbeddingModel(model);
|
||||
if (model) {
|
||||
showToast(`向量记忆已启用(${model})`, 'success');
|
||||
logSetting('向量记忆引擎', `启用: ${model}`);
|
||||
} else {
|
||||
showToast('向量记忆已关闭,仅使用关键词匹配', 'info');
|
||||
logSetting('向量记忆引擎', '关闭');
|
||||
}
|
||||
updateMemoryVectorStatus();
|
||||
});
|
||||
}
|
||||
|
||||
// ── 工作空间目录设置 ──
|
||||
const inputWorkspaceDir = document.querySelector('#inputWorkspaceDir') as HTMLInputElement;
|
||||
const btnBrowseWorkspace = document.querySelector('#btnBrowseWorkspace') as HTMLButtonElement;
|
||||
@@ -290,6 +262,10 @@ export function initSettingsModal(): void {
|
||||
logSetting('工作空间目录', result.dir!);
|
||||
// ── 通知工作空间面板刷新目录 ──
|
||||
window.dispatchEvent(new CustomEvent('workspaceDirChanged', { detail: { dir: result.dir } }));
|
||||
// ── 初始化新工作空间的 MEMORY.md ──
|
||||
import('../services/memory-service.js').then(({ initMemoryFile }) => {
|
||||
initMemoryFile().catch(() => {});
|
||||
});
|
||||
} else {
|
||||
showToast(`设置失败: ${result.error!}`, 'error');
|
||||
// 恢复原值
|
||||
@@ -305,9 +281,6 @@ export function openSettingsModal(): void {
|
||||
(document.querySelector('#inputServerUrl') as HTMLInputElement).value = state.get<OllamaAPI>(KEYS.API)?.baseUrl || '';
|
||||
updateConnectionInfo();
|
||||
updateRunningModels();
|
||||
populateMemoryEmbedModels();
|
||||
populateSubAgentModels();
|
||||
updateMemoryVectorStatus();
|
||||
loadTimeoutSettings();
|
||||
loadWatchdogSetting();
|
||||
// 刷新工作空间目录显示
|
||||
@@ -413,50 +386,6 @@ async function exportAllSessions(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function populateMemoryEmbedModels(): Promise<void> {
|
||||
const select = document.querySelector('#selectMemoryEmbedModel') as HTMLSelectElement;
|
||||
if (!select) return;
|
||||
const api = state.get<OllamaAPI>(KEYS.API);
|
||||
if (!api) return;
|
||||
|
||||
try {
|
||||
const data = await api.listModels();
|
||||
select.innerHTML = '<option value="">未选择(仅关键词模式)</option>';
|
||||
if (!data.models || data.models.length === 0) return;
|
||||
|
||||
// 获取每个模型的能力信息,筛选嵌入模型
|
||||
const checks = await Promise.allSettled(
|
||||
data.models.map(async (m) => {
|
||||
const info = await api.showModel(m.name);
|
||||
const caps = info.capabilities || [];
|
||||
return { name: m.name, size: m.size, isEmbed: caps.includes('embedding') };
|
||||
})
|
||||
);
|
||||
|
||||
const embedModels = checks
|
||||
.filter(r => r.status === 'fulfilled' && r.value.isEmbed)
|
||||
.map(r => (r as PromiseFulfilledResult<{ name: string; size?: number; isEmbed: boolean }>).value);
|
||||
|
||||
embedModels.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.name;
|
||||
opt.textContent = m.name;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
if (embedModels.length === 0) {
|
||||
select.innerHTML = '<option value="">未检测到嵌入模型,请先 ollama pull</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复已保存的选择
|
||||
const saved = getEmbeddingModel();
|
||||
if (saved) select.value = saved;
|
||||
} catch (err) {
|
||||
logWarn('加载嵌入模型列表失败', (err as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
async function populateSubAgentModels(): Promise<void> {
|
||||
const select = document.querySelector('#selectSubAgentModel') as HTMLSelectElement;
|
||||
if (!select) return;
|
||||
@@ -533,20 +462,6 @@ const saveLoopWatchdog = debounce(async () => {
|
||||
}, 500);
|
||||
document.querySelector('#inputLoopWatchdog')!.addEventListener('input', saveLoopWatchdog);
|
||||
|
||||
function updateMemoryVectorStatus(): void {
|
||||
const statusEl = document.querySelector('#memoryVectorStatus');
|
||||
if (!statusEl) return;
|
||||
const model = getEmbeddingModel();
|
||||
if (model) {
|
||||
const memCount = getMemoryCache().length;
|
||||
statusEl.textContent = `✅ 向量搜索已启用(${model})· ${memCount} 条记忆`;
|
||||
(statusEl as HTMLElement).style.color = 'var(--success)';
|
||||
} else {
|
||||
statusEl.textContent = 'ℹ️ 未选择嵌入模型,仅使用关键词匹配';
|
||||
(statusEl as HTMLElement).style.color = '';
|
||||
}
|
||||
}
|
||||
|
||||
async function importSessions(filePath: string): Promise<void> {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (!db) return;
|
||||
|
||||
Reference in New Issue
Block a user