154 lines
6.2 KiB
JavaScript
154 lines
6.2 KiB
JavaScript
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// Story Summary - State Integration (L0)
|
|||
|
|
// 事件监听 + 回滚钩子注册
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
import { getContext } from '../../../../../../extensions.js';
|
|||
|
|
import { xbLog } from '../../../core/debug-core.js';
|
|||
|
|
import {
|
|||
|
|
saveStateAtoms,
|
|||
|
|
saveStateVectors,
|
|||
|
|
deleteStateAtomsFromFloor,
|
|||
|
|
deleteStateVectorsFromFloor,
|
|||
|
|
getStateAtoms,
|
|||
|
|
clearStateVectors,
|
|||
|
|
} from './state-store.js';
|
|||
|
|
import { embed, getEngineFingerprint } from './embedder.js';
|
|||
|
|
import { getVectorConfig } from '../data/config.js';
|
|||
|
|
|
|||
|
|
const MODULE_ID = 'state-integration';
|
|||
|
|
|
|||
|
|
let initialized = false;
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// 初始化
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
export function initStateIntegration() {
|
|||
|
|
if (initialized) return;
|
|||
|
|
initialized = true;
|
|||
|
|
|
|||
|
|
// 监听变量团队的事件
|
|||
|
|
$(document).on('xiaobaix:variables:stateAtomsGenerated', handleStateAtomsGenerated);
|
|||
|
|
|
|||
|
|
// 注册回滚钩子
|
|||
|
|
globalThis.LWB_StateRollbackHook = handleStateRollback;
|
|||
|
|
|
|||
|
|
xbLog.info(MODULE_ID, 'L0 状态层集成已初始化');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// 事件处理
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function handleStateAtomsGenerated(e, data) {
|
|||
|
|
const { atoms } = data || {};
|
|||
|
|
if (!atoms?.length) return;
|
|||
|
|
|
|||
|
|
const { chatId } = getContext();
|
|||
|
|
if (!chatId) return;
|
|||
|
|
|
|||
|
|
const validAtoms = atoms.filter(a => a?.chatId === chatId);
|
|||
|
|
if (!validAtoms.length) {
|
|||
|
|
xbLog.warn(MODULE_ID, `atoms.chatId 不匹配,期望 ${chatId},跳过`);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
xbLog.info(MODULE_ID, `收到 ${validAtoms.length} 个 StateAtom`);
|
|||
|
|
|
|||
|
|
// 1. 存入 chat_metadata(持久化)
|
|||
|
|
saveStateAtoms(validAtoms);
|
|||
|
|
|
|||
|
|
// 2. 向量化并存入 IndexedDB
|
|||
|
|
const vectorCfg = getVectorConfig();
|
|||
|
|
if (!vectorCfg?.enabled) {
|
|||
|
|
xbLog.info(MODULE_ID, '向量未启用,跳过 L0 向量化');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await vectorizeAtoms(chatId, validAtoms, vectorCfg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function vectorizeAtoms(chatId, atoms, vectorCfg) {
|
|||
|
|
const texts = atoms.map(a => a.semantic);
|
|||
|
|
const fingerprint = getEngineFingerprint(vectorCfg);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const vectors = await embed(texts, vectorCfg);
|
|||
|
|
|
|||
|
|
const items = atoms.map((a, i) => ({
|
|||
|
|
atomId: a.atomId,
|
|||
|
|
floor: a.floor,
|
|||
|
|
vector: vectors[i],
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
await saveStateVectors(chatId, items, fingerprint);
|
|||
|
|
xbLog.info(MODULE_ID, `L0 向量化完成: ${items.length} 个`);
|
|||
|
|
} catch (e) {
|
|||
|
|
xbLog.error(MODULE_ID, 'L0 向量化失败', e);
|
|||
|
|
// 不阻塞,向量可后续通过"生成向量"重建
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// 回滚钩子
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
async function handleStateRollback(floor) {
|
|||
|
|
xbLog.info(MODULE_ID, `收到回滚请求: floor >= ${floor}`);
|
|||
|
|
|
|||
|
|
const { chatId } = getContext();
|
|||
|
|
|
|||
|
|
// 1. 删除 chat_metadata 中的 atoms
|
|||
|
|
deleteStateAtomsFromFloor(floor);
|
|||
|
|
|
|||
|
|
// 2. 删除 IndexedDB 中的 vectors
|
|||
|
|
if (chatId) {
|
|||
|
|
await deleteStateVectorsFromFloor(chatId, floor);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// 重建向量(供"生成向量"按钮调用)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
export async function rebuildStateVectors(chatId, vectorCfg) {
|
|||
|
|
if (!chatId || !vectorCfg?.enabled) return { built: 0 };
|
|||
|
|
|
|||
|
|
const atoms = getStateAtoms();
|
|||
|
|
if (!atoms.length) return { built: 0 };
|
|||
|
|
|
|||
|
|
xbLog.info(MODULE_ID, `开始重建 L0 向量: ${atoms.length} 个 atom`);
|
|||
|
|
|
|||
|
|
// 清空旧向量
|
|||
|
|
await clearStateVectors(chatId);
|
|||
|
|
|
|||
|
|
// 重新向量化
|
|||
|
|
const fingerprint = getEngineFingerprint(vectorCfg);
|
|||
|
|
const batchSize = vectorCfg.engine === 'local' ? 5 : 25;
|
|||
|
|
let built = 0;
|
|||
|
|
|
|||
|
|
for (let i = 0; i < atoms.length; i += batchSize) {
|
|||
|
|
const batch = atoms.slice(i, i + batchSize);
|
|||
|
|
const texts = batch.map(a => a.semantic);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const vectors = await embed(texts, vectorCfg);
|
|||
|
|
|
|||
|
|
const items = batch.map((a, j) => ({
|
|||
|
|
atomId: a.atomId,
|
|||
|
|
floor: a.floor,
|
|||
|
|
vector: vectors[j],
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
await saveStateVectors(chatId, items, fingerprint);
|
|||
|
|
built += items.length;
|
|||
|
|
} catch (e) {
|
|||
|
|
xbLog.error(MODULE_ID, `L0 向量化批次失败: ${i}-${i + batchSize}`, e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
xbLog.info(MODULE_ID, `L0 向量重建完成: ${built}/${atoms.length}`);
|
|||
|
|
return { built };
|
|||
|
|
}
|