Add L0 index and anchor UI updates
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Story Summary - 主入口(最终版)
|
||||
//
|
||||
// 稳定目标:
|
||||
@@ -43,18 +43,7 @@ import {
|
||||
import { runSummaryGeneration } from "./generate/generator.js";
|
||||
|
||||
// vector service
|
||||
import {
|
||||
embed,
|
||||
getEngineFingerprint,
|
||||
checkLocalModelStatus,
|
||||
downloadLocalModel,
|
||||
cancelDownload,
|
||||
deleteLocalModelCache,
|
||||
testOnlineService,
|
||||
fetchOnlineModels,
|
||||
isLocalModelLoaded,
|
||||
DEFAULT_LOCAL_MODEL,
|
||||
} from "./vector/utils/embedder.js";
|
||||
import { embed, getEngineFingerprint, testOnlineService } from "./vector/utils/embedder.js";
|
||||
|
||||
import {
|
||||
getMeta,
|
||||
@@ -76,8 +65,20 @@ import {
|
||||
syncOnMessageSwiped,
|
||||
syncOnMessageReceived,
|
||||
} from "./vector/pipeline/chunk-builder.js";
|
||||
import { initStateIntegration, rebuildStateVectors } from "./vector/pipeline/state-integration.js";
|
||||
import { clearStateVectors, getStateAtomsCount, getStateVectorsCount } from "./vector/storage/state-store.js";
|
||||
import {
|
||||
incrementalExtractAtoms,
|
||||
clearAllAtomsAndVectors,
|
||||
cancelL0Extraction,
|
||||
getAnchorStats,
|
||||
initStateIntegration,
|
||||
} from "./vector/pipeline/state-integration.js";
|
||||
import {
|
||||
clearStateVectors,
|
||||
getStateAtoms,
|
||||
getStateAtomsCount,
|
||||
getStateVectorsCount,
|
||||
saveStateVectors,
|
||||
} from "./vector/storage/state-store.js";
|
||||
|
||||
// vector io
|
||||
import { exportVectors, importVectors } from "./vector/storage/vector-io.js";
|
||||
@@ -105,6 +106,7 @@ let eventsRegistered = false;
|
||||
let vectorGenerating = false;
|
||||
let vectorCancelled = false;
|
||||
let vectorAbortController = null;
|
||||
let anchorGenerating = false;
|
||||
|
||||
// ★ 用户消息缓存(解决 GENERATION_STARTED 时 chat 尚未包含用户消息的问题)
|
||||
let lastSentUserMessage = null;
|
||||
@@ -213,6 +215,7 @@ function flushPendingFrameMessages() {
|
||||
if (!iframe?.contentWindow) return;
|
||||
pendingFrameMessages.forEach((p) => postToIframe(iframe, p, "LittleWhiteBox"));
|
||||
pendingFrameMessages = [];
|
||||
sendAnchorStatsToFrame();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -260,49 +263,66 @@ async function sendVectorStatsToFrame() {
|
||||
});
|
||||
}
|
||||
|
||||
async function sendLocalModelStatusToFrame(modelId) {
|
||||
if (!modelId) {
|
||||
const cfg = getVectorConfig();
|
||||
modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||
}
|
||||
const status = await checkLocalModelStatus(modelId);
|
||||
postToFrame({
|
||||
type: "VECTOR_LOCAL_MODEL_STATUS",
|
||||
status: status.status,
|
||||
message: status.message,
|
||||
});
|
||||
async function sendAnchorStatsToFrame() {
|
||||
const stats = await getAnchorStats();
|
||||
postToFrame({ type: "ANCHOR_STATS", stats });
|
||||
}
|
||||
|
||||
async function handleDownloadLocalModel(modelId) {
|
||||
try {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "downloading", message: "下载中..." });
|
||||
async function handleAnchorGenerate() {
|
||||
if (anchorGenerating) return;
|
||||
|
||||
await downloadLocalModel(modelId, (percent) => {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_PROGRESS", percent });
|
||||
const vectorCfg = getVectorConfig();
|
||||
if (!vectorCfg?.enabled) {
|
||||
await executeSlashCommand("/echo severity=warning 请先启用向量检索");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vectorCfg.online?.key) {
|
||||
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: "请配置 API Key" });
|
||||
return;
|
||||
}
|
||||
|
||||
const { chatId, chat } = getContext();
|
||||
if (!chatId || !chat?.length) return;
|
||||
|
||||
anchorGenerating = true;
|
||||
|
||||
postToFrame({ type: "ANCHOR_GEN_PROGRESS", current: 0, total: 1, message: "分析中..." });
|
||||
|
||||
try {
|
||||
await incrementalExtractAtoms(chatId, chat, (message, current, total) => {
|
||||
postToFrame({ type: "ANCHOR_GEN_PROGRESS", current, total, message });
|
||||
});
|
||||
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "ready", message: "已就绪" });
|
||||
await sendAnchorStatsToFrame();
|
||||
await sendVectorStatsToFrame();
|
||||
|
||||
xbLog.info(MODULE_ID, "记忆锚点生成完成");
|
||||
} catch (e) {
|
||||
if (e.message === "下载已取消") {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "not_downloaded", message: "已取消" });
|
||||
} else {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "error", message: e.message });
|
||||
}
|
||||
xbLog.error(MODULE_ID, "记忆锚点生成失败", e);
|
||||
await executeSlashCommand(`/echo severity=error 记忆锚点生成失败:${e.message}`);
|
||||
} finally {
|
||||
anchorGenerating = false;
|
||||
postToFrame({ type: "ANCHOR_GEN_PROGRESS", current: -1, total: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancelDownload() {
|
||||
cancelDownload();
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "not_downloaded", message: "已取消" });
|
||||
async function handleAnchorClear() {
|
||||
const { chatId } = getContext();
|
||||
if (!chatId) return;
|
||||
|
||||
await clearAllAtomsAndVectors(chatId);
|
||||
await sendAnchorStatsToFrame();
|
||||
await sendVectorStatsToFrame();
|
||||
|
||||
await executeSlashCommand("/echo severity=info 记忆锚点已清空");
|
||||
xbLog.info(MODULE_ID, "记忆锚点已清空");
|
||||
}
|
||||
|
||||
async function handleDeleteLocalModel(modelId) {
|
||||
try {
|
||||
await deleteLocalModelCache(modelId);
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "not_downloaded", message: "未下载" });
|
||||
} catch (e) {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "error", message: e.message });
|
||||
}
|
||||
function handleAnchorCancel() {
|
||||
cancelL0Extraction();
|
||||
anchorGenerating = false;
|
||||
postToFrame({ type: "ANCHOR_GEN_PROGRESS", current: -1, total: 0 });
|
||||
}
|
||||
|
||||
async function handleTestOnlineService(provider, config) {
|
||||
@@ -319,75 +339,70 @@ async function handleTestOnlineService(provider, config) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetchOnlineModels(config) {
|
||||
try {
|
||||
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "downloading", message: "拉取中..." });
|
||||
const models = await fetchOnlineModels(config);
|
||||
postToFrame({ type: "VECTOR_ONLINE_MODELS", models });
|
||||
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "success", message: `找到 ${models.length} 个模型` });
|
||||
} catch (e) {
|
||||
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGenerateVectors(vectorCfg) {
|
||||
if (vectorGenerating) return;
|
||||
|
||||
if (!vectorCfg?.enabled) {
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: -1, total: 0 });
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: -1, total: 0 });
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "ALL", current: -1, total: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const { chatId, chat } = getContext();
|
||||
if (!chatId || !chat?.length) return;
|
||||
|
||||
if (vectorCfg.engine === "online") {
|
||||
if (!vectorCfg.online?.key || !vectorCfg.online?.model) {
|
||||
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: "请配置在线服务 API" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (vectorCfg.engine === "local") {
|
||||
const modelId = vectorCfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||
const status = await checkLocalModelStatus(modelId);
|
||||
if (status.status !== "ready") {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "downloading", message: "正在加载模型..." });
|
||||
try {
|
||||
await downloadLocalModel(modelId, (percent) => {
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_PROGRESS", percent });
|
||||
});
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "ready", message: "已就绪" });
|
||||
} catch (e) {
|
||||
xbLog.error(MODULE_ID, "模型加载失败", e);
|
||||
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "error", message: e.message });
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!vectorCfg.online?.key) {
|
||||
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: "请配置 API Key" });
|
||||
return;
|
||||
}
|
||||
|
||||
vectorGenerating = true;
|
||||
vectorCancelled = false;
|
||||
vectorAbortController?.abort?.();
|
||||
vectorAbortController = new AbortController();
|
||||
|
||||
const fingerprint = getEngineFingerprint(vectorCfg);
|
||||
const isLocal = vectorCfg.engine === "local";
|
||||
const batchSize = isLocal ? 5 : 25;
|
||||
const concurrency = isLocal ? 1 : 2;
|
||||
|
||||
// L0 向量重建
|
||||
try {
|
||||
await rebuildStateVectors(chatId, vectorCfg);
|
||||
} catch (e) {
|
||||
xbLog.error(MODULE_ID, "L0 向量重建失败", e);
|
||||
// 不阻塞,继续 L1/L2
|
||||
}
|
||||
const batchSize = 20;
|
||||
|
||||
await clearAllChunks(chatId);
|
||||
await clearEventVectors(chatId);
|
||||
await clearStateVectors(chatId);
|
||||
await updateMeta(chatId, { lastChunkFloor: -1, fingerprint });
|
||||
|
||||
const atoms = getStateAtoms();
|
||||
if (!atoms.length) {
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: 0, total: 0, message: "L0 为空,跳过" });
|
||||
} else {
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: 0, total: atoms.length, message: "L0 向量化..." });
|
||||
|
||||
let l0Completed = 0;
|
||||
for (let i = 0; i < atoms.length; i += batchSize) {
|
||||
if (vectorCancelled) break;
|
||||
|
||||
const batch = atoms.slice(i, i + batchSize);
|
||||
const texts = batch.map(a => a.semantic);
|
||||
try {
|
||||
const vectors = await embed(texts, vectorCfg, { signal: vectorAbortController.signal });
|
||||
const items = batch.map((a, j) => ({
|
||||
atomId: a.atomId,
|
||||
floor: a.floor,
|
||||
vector: vectors[j],
|
||||
}));
|
||||
await saveStateVectors(chatId, items, fingerprint);
|
||||
l0Completed += batch.length;
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L0", current: l0Completed, total: atoms.length });
|
||||
} catch (e) {
|
||||
if (e?.name === "AbortError") break;
|
||||
xbLog.error(MODULE_ID, "L0 向量化失败", e);
|
||||
vectorCancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vectorCancelled) {
|
||||
vectorGenerating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const allChunks = [];
|
||||
for (let floor = 0; floor < chat.length; floor++) {
|
||||
const chunks = chunkMessage(floor, chat[floor]);
|
||||
@@ -398,148 +413,82 @@ async function handleGenerateVectors(vectorCfg) {
|
||||
await saveChunks(chatId, allChunks);
|
||||
}
|
||||
|
||||
const l1Texts = allChunks.map((c) => c.text);
|
||||
const l1Batches = [];
|
||||
for (let i = 0; i < l1Texts.length; i += batchSize) {
|
||||
l1Batches.push({
|
||||
phase: "L1",
|
||||
texts: l1Texts.slice(i, i + batchSize),
|
||||
startIdx: i,
|
||||
});
|
||||
}
|
||||
|
||||
const l1Texts = allChunks.map(c => c.text);
|
||||
const store = getSummaryStore();
|
||||
const events = store?.json?.events || [];
|
||||
|
||||
// L2: 全量重建(先清空再重建,保持与 L1 一致性)
|
||||
await clearEventVectors(chatId);
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: 0, total: l1Texts.length });
|
||||
|
||||
const l2Pairs = events
|
||||
.map((e) => ({ id: e.id, text: `${e.title || ""} ${e.summary || ""}`.trim() }))
|
||||
.filter((p) => p.text);
|
||||
const l1Vectors = [];
|
||||
let completed = 0;
|
||||
for (let i = 0; i < l1Texts.length; i += batchSize) {
|
||||
if (vectorCancelled) break;
|
||||
|
||||
const l2Batches = [];
|
||||
for (let i = 0; i < l2Pairs.length; i += batchSize) {
|
||||
const batch = l2Pairs.slice(i, i + batchSize);
|
||||
l2Batches.push({
|
||||
phase: "L2",
|
||||
texts: batch.map((p) => p.text),
|
||||
ids: batch.map((p) => p.id),
|
||||
startIdx: i,
|
||||
});
|
||||
}
|
||||
|
||||
const l1Total = allChunks.length;
|
||||
const l2Total = events.length;
|
||||
let l1Completed = 0;
|
||||
let l2Completed = 0;
|
||||
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: 0, total: l1Total });
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: l2Completed, total: l2Total });
|
||||
|
||||
let rateLimitWarned = false;
|
||||
|
||||
const allTasks = [...l1Batches, ...l2Batches];
|
||||
const l1Vectors = new Array(l1Texts.length);
|
||||
const l2VectorItems = [];
|
||||
|
||||
let taskIndex = 0;
|
||||
|
||||
async function worker() {
|
||||
while (taskIndex < allTasks.length) {
|
||||
if (vectorCancelled) break;
|
||||
if (vectorAbortController?.signal?.aborted) break;
|
||||
|
||||
const i = taskIndex++;
|
||||
if (i >= allTasks.length) break;
|
||||
|
||||
const task = allTasks[i];
|
||||
|
||||
try {
|
||||
const vectors = await embed(task.texts, vectorCfg, { signal: vectorAbortController.signal });
|
||||
|
||||
if (task.phase === "L1") {
|
||||
for (let j = 0; j < vectors.length; j++) {
|
||||
l1Vectors[task.startIdx + j] = vectors[j];
|
||||
}
|
||||
l1Completed += task.texts.length;
|
||||
postToFrame({
|
||||
type: "VECTOR_GEN_PROGRESS",
|
||||
phase: "L1",
|
||||
current: Math.min(l1Completed, l1Total),
|
||||
total: l1Total,
|
||||
});
|
||||
} else {
|
||||
for (let j = 0; j < vectors.length; j++) {
|
||||
l2VectorItems.push({ eventId: task.ids[j], vector: vectors[j] });
|
||||
}
|
||||
l2Completed += task.texts.length;
|
||||
postToFrame({
|
||||
type: "VECTOR_GEN_PROGRESS",
|
||||
phase: "L2",
|
||||
current: Math.min(l2Completed, l2Total),
|
||||
total: l2Total,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (e?.name === "AbortError") {
|
||||
xbLog.warn(MODULE_ID, "向量生成已取消(AbortError)");
|
||||
break;
|
||||
}
|
||||
|
||||
xbLog.error(MODULE_ID, `${task.phase} batch 向量化失败`, e);
|
||||
|
||||
const msg = String(e?.message || e);
|
||||
const isRateLike = /429|403|rate|limit|quota/i.test(msg);
|
||||
if (isRateLike && !rateLimitWarned) {
|
||||
rateLimitWarned = true;
|
||||
executeSlashCommand("/echo severity=warning 向量生成遇到速率/配额限制,已进入自动重试。");
|
||||
}
|
||||
|
||||
vectorCancelled = true;
|
||||
vectorAbortController?.abort?.();
|
||||
break;
|
||||
}
|
||||
const batch = l1Texts.slice(i, i + batchSize);
|
||||
try {
|
||||
const vectors = await embed(batch, vectorCfg, { signal: vectorAbortController.signal });
|
||||
l1Vectors.push(...vectors);
|
||||
completed += batch.length;
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: completed, total: l1Texts.length });
|
||||
} catch (e) {
|
||||
if (e?.name === 'AbortError') break;
|
||||
xbLog.error(MODULE_ID, 'L1 向量化失败', e);
|
||||
vectorCancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array(Math.min(concurrency, allTasks.length))
|
||||
.fill(null)
|
||||
.map(() => worker())
|
||||
);
|
||||
|
||||
if (vectorCancelled || vectorAbortController?.signal?.aborted) {
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: -1, total: 0 });
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: -1, total: 0 });
|
||||
vectorGenerating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (allChunks.length > 0 && l1Vectors.filter(Boolean).length > 0) {
|
||||
const chunkVectorItems = allChunks
|
||||
.map((chunk, idx) => (l1Vectors[idx] ? { chunkId: chunk.chunkId, vector: l1Vectors[idx] } : null))
|
||||
.filter(Boolean);
|
||||
await saveChunkVectors(chatId, chunkVectorItems, fingerprint);
|
||||
if (!vectorCancelled && l1Vectors.length > 0) {
|
||||
const items = allChunks.map((c, i) => ({ chunkId: c.chunkId, vector: l1Vectors[i] })).filter(x => x.vector);
|
||||
await saveChunkVectors(chatId, items, fingerprint);
|
||||
await updateMeta(chatId, { lastChunkFloor: chat.length - 1 });
|
||||
}
|
||||
|
||||
if (l2VectorItems.length > 0) {
|
||||
await saveEventVectorsToDb(chatId, l2VectorItems, fingerprint);
|
||||
const l2Pairs = events
|
||||
.map(e => ({ id: e.id, text: `${e.title || ''} ${e.summary || ''}`.trim() }))
|
||||
.filter(p => p.text);
|
||||
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: 0, total: l2Pairs.length });
|
||||
let l2Completed = 0;
|
||||
for (let i = 0; i < l2Pairs.length; i += batchSize) {
|
||||
if (vectorCancelled) break;
|
||||
|
||||
const batch = l2Pairs.slice(i, i + batchSize);
|
||||
try {
|
||||
const vectors = await embed(batch.map(p => p.text), vectorCfg, { signal: vectorAbortController.signal });
|
||||
const items = batch.map((p, j) => ({ eventId: p.id, vector: vectors[j] }));
|
||||
await saveEventVectorsToDb(chatId, items, fingerprint);
|
||||
l2Completed += batch.length;
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: l2Completed, total: l2Pairs.length });
|
||||
} catch (e) {
|
||||
if (e?.name === 'AbortError') break;
|
||||
xbLog.error(MODULE_ID, 'L2 向量化失败', e);
|
||||
vectorCancelled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 fingerprint(无论之前是否匹配)
|
||||
await updateMeta(chatId, { fingerprint });
|
||||
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: -1, total: 0 });
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: -1, total: 0 });
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "ALL", current: -1, total: 0 });
|
||||
await sendVectorStatsToFrame();
|
||||
|
||||
vectorGenerating = false;
|
||||
vectorCancelled = false;
|
||||
vectorAbortController = null;
|
||||
|
||||
xbLog.info(MODULE_ID, `向量生成完成: L1=${l1Vectors.filter(Boolean).length}, L2=${l2VectorItems.length}`);
|
||||
xbLog.info(MODULE_ID, `向量生成完成: L0=${atoms.length}, L1=${l1Vectors.length}, L2=${l2Pairs.length}`);
|
||||
}
|
||||
|
||||
async function handleClearVectors() {
|
||||
const { chatId } = getContext();
|
||||
if (!chatId) return;
|
||||
|
||||
await clearEventVectors(chatId);
|
||||
await clearAllChunks(chatId);
|
||||
await clearStateVectors(chatId);
|
||||
await updateMeta(chatId, { lastChunkFloor: -1 });
|
||||
await sendVectorStatsToFrame();
|
||||
await executeSlashCommand('/echo severity=info 向量数据已清除。如需恢复召回功能,请重新点击"生成向量"。');
|
||||
xbLog.info(MODULE_ID, "向量数据已清除");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -555,20 +504,10 @@ async function autoVectorizeNewEvents(newEventIds) {
|
||||
const { chatId } = getContext();
|
||||
if (!chatId) return;
|
||||
|
||||
// 本地模型未加载时跳过(不阻塞总结流程)
|
||||
if (vectorCfg.engine === "local") {
|
||||
const modelId = vectorCfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||
if (!isLocalModelLoaded(modelId)) {
|
||||
xbLog.warn(MODULE_ID, "L2 自动向量化跳过:本地模型未加载");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const store = getSummaryStore();
|
||||
const events = store?.json?.events || [];
|
||||
const newEventIdSet = new Set(newEventIds);
|
||||
|
||||
// 只取本次新增的 events
|
||||
const newEvents = events.filter((e) => newEventIdSet.has(e.id));
|
||||
if (!newEvents.length) return;
|
||||
|
||||
@@ -580,7 +519,7 @@ async function autoVectorizeNewEvents(newEventIds) {
|
||||
|
||||
try {
|
||||
const fingerprint = getEngineFingerprint(vectorCfg);
|
||||
const batchSize = vectorCfg.engine === "local" ? 5 : 25;
|
||||
const batchSize = 20;
|
||||
|
||||
for (let i = 0; i < pairs.length; i += batchSize) {
|
||||
const batch = pairs.slice(i, i + batchSize);
|
||||
@@ -599,7 +538,6 @@ async function autoVectorizeNewEvents(newEventIds) {
|
||||
await sendVectorStatsToFrame();
|
||||
} catch (e) {
|
||||
xbLog.error(MODULE_ID, "L2 自动向量化失败", e);
|
||||
// 不抛出,不阻塞总结流程
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,7 +555,6 @@ async function syncEventVectorsOnEdit(oldEvents, newEvents) {
|
||||
const oldIds = new Set((oldEvents || []).map((e) => e.id).filter(Boolean));
|
||||
const newIds = new Set((newEvents || []).map((e) => e.id).filter(Boolean));
|
||||
|
||||
// 找出被删除的 eventIds
|
||||
const deletedIds = [...oldIds].filter((id) => !newIds.has(id));
|
||||
|
||||
if (deletedIds.length > 0) {
|
||||
@@ -635,7 +572,6 @@ async function checkVectorIntegrityAndWarn() {
|
||||
const vectorCfg = getVectorConfig();
|
||||
if (!vectorCfg?.enabled) return;
|
||||
|
||||
// 节流:2分钟内不重复提醒
|
||||
const now = Date.now();
|
||||
if (now - lastVectorWarningAt < VECTOR_WARNING_COOLDOWN_MS) return;
|
||||
|
||||
@@ -646,7 +582,6 @@ async function checkVectorIntegrityAndWarn() {
|
||||
const totalFloors = chat.length;
|
||||
const totalEvents = store?.json?.events?.length || 0;
|
||||
|
||||
// 如果没有总结数据,不需要向量
|
||||
if (totalEvents === 0) return;
|
||||
|
||||
const meta = await getMeta(chatId);
|
||||
@@ -655,18 +590,15 @@ async function checkVectorIntegrityAndWarn() {
|
||||
|
||||
const issues = [];
|
||||
|
||||
// 指纹不匹配
|
||||
if (meta.fingerprint && meta.fingerprint !== fingerprint) {
|
||||
issues.push('向量引擎/模型已变更');
|
||||
}
|
||||
|
||||
// L1 不完整
|
||||
const chunkFloorGap = totalFloors - 1 - (meta.lastChunkFloor ?? -1);
|
||||
if (chunkFloorGap > 0) {
|
||||
issues.push(`${chunkFloorGap} 层片段未向量化`);
|
||||
}
|
||||
|
||||
// L2 不完整
|
||||
const eventVectorGap = totalEvents - stats.eventVectors;
|
||||
if (eventVectorGap > 0) {
|
||||
issues.push(`${eventVectorGap} 个事件未向量化`);
|
||||
@@ -678,19 +610,6 @@ async function checkVectorIntegrityAndWarn() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClearVectors() {
|
||||
const { chatId } = getContext();
|
||||
if (!chatId) return;
|
||||
|
||||
await clearEventVectors(chatId);
|
||||
await clearAllChunks(chatId);
|
||||
await clearStateVectors(chatId);
|
||||
await updateMeta(chatId, { lastChunkFloor: -1 });
|
||||
await sendVectorStatsToFrame();
|
||||
await executeSlashCommand('/echo severity=info 向量数据已清除。如需恢复召回功能,请重新点击"生成向量"。');
|
||||
xbLog.info(MODULE_ID, "向量数据已清除");
|
||||
}
|
||||
|
||||
async function maybeAutoBuildChunks() {
|
||||
const cfg = getVectorConfig();
|
||||
if (!cfg?.enabled) return;
|
||||
@@ -701,11 +620,6 @@ async function maybeAutoBuildChunks() {
|
||||
const status = await getChunkBuildStatus();
|
||||
if (status.pending <= 0) return;
|
||||
|
||||
if (cfg.engine === "local") {
|
||||
const modelId = cfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||
if (!isLocalModelLoaded(modelId)) return;
|
||||
}
|
||||
|
||||
try {
|
||||
await buildIncrementalChunks({ vectorConfig: cfg });
|
||||
} catch (e) {
|
||||
@@ -887,10 +801,6 @@ function openPanelForMessage(mesId) {
|
||||
|
||||
sendVectorConfigToFrame();
|
||||
sendVectorStatsToFrame();
|
||||
|
||||
const cfg = getVectorConfig();
|
||||
const modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||
sendLocalModelStatusToFrame(modelId);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -1042,10 +952,7 @@ function handleFrameMessage(event) {
|
||||
sendSavedConfigToFrame();
|
||||
sendVectorConfigToFrame();
|
||||
sendVectorStatsToFrame();
|
||||
|
||||
const cfg = getVectorConfig();
|
||||
const modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||
sendLocalModelStatusToFrame(modelId);
|
||||
sendAnchorStatsToFrame();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1074,30 +981,10 @@ function handleFrameMessage(event) {
|
||||
postToFrame({ type: "SUMMARY_STATUS", statusText: "已停止" });
|
||||
break;
|
||||
|
||||
case "VECTOR_DOWNLOAD_MODEL":
|
||||
handleDownloadLocalModel(data.modelId);
|
||||
break;
|
||||
|
||||
case "VECTOR_CANCEL_DOWNLOAD":
|
||||
handleCancelDownload();
|
||||
break;
|
||||
|
||||
case "VECTOR_DELETE_MODEL":
|
||||
handleDeleteLocalModel(data.modelId);
|
||||
break;
|
||||
|
||||
case "VECTOR_CHECK_LOCAL_MODEL":
|
||||
sendLocalModelStatusToFrame(data.modelId);
|
||||
break;
|
||||
|
||||
case "VECTOR_TEST_ONLINE":
|
||||
handleTestOnlineService(data.provider, data.config);
|
||||
break;
|
||||
|
||||
case "VECTOR_FETCH_MODELS":
|
||||
handleFetchOnlineModels(data.config);
|
||||
break;
|
||||
|
||||
case "VECTOR_GENERATE":
|
||||
if (data.config) saveVectorConfig(data.config);
|
||||
handleGenerateVectors(data.config);
|
||||
@@ -1109,7 +996,25 @@ function handleFrameMessage(event) {
|
||||
|
||||
case "VECTOR_CANCEL_GENERATE":
|
||||
vectorCancelled = true;
|
||||
cancelL0Extraction();
|
||||
try { vectorAbortController?.abort?.(); } catch {}
|
||||
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "ALL", current: -1, total: 0 });
|
||||
break;
|
||||
|
||||
case "ANCHOR_GENERATE":
|
||||
handleAnchorGenerate();
|
||||
break;
|
||||
|
||||
case "ANCHOR_CLEAR":
|
||||
handleAnchorClear();
|
||||
break;
|
||||
|
||||
case "ANCHOR_CANCEL":
|
||||
handleAnchorCancel();
|
||||
break;
|
||||
|
||||
case "REQUEST_ANCHOR_STATS":
|
||||
sendAnchorStatsToFrame();
|
||||
break;
|
||||
|
||||
case "VECTOR_EXPORT":
|
||||
|
||||
Reference in New Issue
Block a user