feat: v4.0.0 大版本升级 - SQLite3 + ReAct Agent

- P0: 存储层 IndexedDB → SQLite3 (better-sqlite3),7张表 + FTS5全文搜索
- P1: Agent Engine 升级为 ReAct 模式(思考→行动→观察→反思)
- P2: 新增 4 个工具(memory_search, memory_add, session_list, session_read)
- P3: 上下文管理器(滑动窗口 + 摘要压缩 + 记忆注入)
- P4: UI 改版(ReAct 执行面板样式 + 思考过程卡片)
- P5: IndexedDB → SQLite 数据迁移支持
- MAX_LOOPS 10→15, MAX_LOOP_TIME 5min→10min, 错误自动重试 2 次
This commit is contained in:
thzxx
2026-04-17 12:35:42 +08:00
parent 5fb36a93e6
commit 26cbac71fe
14 changed files with 2001 additions and 332 deletions
+117 -1
View File
@@ -29,6 +29,120 @@ import { initLogPanel, addLog } from './services/log-service.js';
import { logInfo, logSuccess, logError, logDebug, logInit, logWarn } from './services/log-service.js';
import type { ChatSession } from './types.js';
// ─── v4.0 数据迁移:IndexedDB → SQLite ───
async function migrateIndexedDBToSQLite(db: ChatDB): Promise<void> {
const bridge = (window as any).metonaDesktop;
if (!bridge?.isDesktop || !bridge?.db) {
logInit('非桌面环境,跳过迁移');
return;
}
// 检查是否已迁移
const migrated = await bridge.db.getSetting('_indexeddb_migrated', false);
if (migrated) {
logInit('SQLite 数据库已连接(已迁移)');
return;
}
// 检查 IndexedDB 中是否有数据
const idb = db.getRawIDB();
if (!idb) {
logInit('SQLite 数据库已连接(无旧数据)');
return;
}
// 读取 IndexedDB 数据
try {
const sessions: ChatSession[] = await new Promise((resolve, reject) => {
const tx = idb.transaction('sessions', 'readonly');
const store = tx.objectStore('sessions');
const req = store.getAll();
req.onsuccess = () => resolve(req.result || []);
req.onerror = () => reject(req.error);
});
const memories: any[] = await new Promise((resolve, reject) => {
const tx = idb.transaction('memories', 'readonly');
const store = tx.objectStore('memories');
const req = store.getAll();
req.onsuccess = () => resolve(req.result || []);
req.onerror = () => reject(req.error);
});
const settings: any[] = await new Promise((resolve, reject) => {
const tx = idb.transaction('settings', 'readonly');
const store = tx.objectStore('settings');
const req = store.getAll();
req.onsuccess = () => resolve(req.result || []);
req.onerror = () => reject(req.error);
});
if (sessions.length === 0 && memories.length === 0 && settings.length === 0) {
logInit('SQLite 数据库已连接(无旧数据)');
await bridge.db.saveSetting('_indexeddb_migrated', true);
return;
}
logInit(`发现旧数据: ${sessions.length} 会话, ${memories.length} 记忆, ${settings.length} 设置`);
// 构造导入数据
const importData = {
sessions: sessions.map(s => ({
id: s.id,
title: s.title,
model: s.model,
system_prompt: null as string | null,
parent_id: null as string | null,
status: 'active',
created_at: s.createdAt,
updated_at: s.updatedAt
})),
messages: sessions.flatMap(s =>
s.messages.map((m: any, idx: number) => ({
id: `${s.id}_msg_${idx}_${m.timestamp}`,
session_id: s.id,
role: m.role,
content: m.content || null,
thinking: m.think || null,
images: m.images?.length ? JSON.stringify(m.images) : null,
tool_calls: m.toolCalls?.length ? JSON.stringify(m.toolCalls) : null,
tool_name: null,
eval_count: m.eval_count || null,
total_duration: m.total_duration || null,
created_at: m.timestamp || Date.now()
}))
),
memories: memories.map((m: any) => ({
id: m.id,
type: m.type,
content: m.content,
importance: m.importance || 5,
tags: m.tags?.length ? JSON.stringify(m.tags) : null,
source: m.source || null,
session_id: m.sessionId || null,
use_count: m.useCount || 0,
embedding: m.embedding ? JSON.stringify(m.embedding) : null,
created_at: m.createdAt,
updated_at: m.updatedAt,
last_used_at: m.lastUsedAt
})),
settings: settings.map((s: any) => ({ key: s.key, value: s.value })),
exportedAt: Date.now()
};
const result = await bridge.db.importSessions(importData);
logInit(`迁移完成: 导入 ${result.imported} 个会话, 跳过 ${result.skipped}`);
// 标记迁移完成
await bridge.db.saveSetting('_indexeddb_migrated', true);
logSuccess('数据迁移完成');
} catch (err) {
logError('数据迁移失败', (err as Error).message);
// 迁移失败不阻塞启动
}
}
function initHelpModal(): void {
const helpModal = document.querySelector('#helpModal') as HTMLElement;
document.querySelector('#btnHelp')!.addEventListener('click', () => {
@@ -176,7 +290,9 @@ async function init(): Promise<void> {
db = new ChatDB();
await db.init();
state.set(KEYS.DB, db);
logInit('IndexedDB 已连接');
// v4.0 数据迁移:检测 IndexedDB 是否有旧数据需要迁移到 SQLite
await migrateIndexedDBToSQLite(db);
const serverUrl = await db.getSetting('serverUrl', 'http://127.0.0.1:11434');
api = new OllamaAPI(serverUrl);