Improve vector recall error handling
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,9 @@
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// Story Summary - 主入口
|
// Story Summary - 主入口(干净版)
|
||||||
// UI 交互、事件监听、iframe 通讯
|
// - 注入只在 GENERATION_STARTED 发生
|
||||||
|
// - 向量关闭:注入全量总结(L3+L2+Arcs)
|
||||||
|
// - 向量开启:召回 + 1万预算装配注入
|
||||||
|
// - 删除所有 updateSummaryExtensionPrompt() 调用,避免覆盖/残留/竞态
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
import { getContext } from "../../../../../extensions.js";
|
import { getContext } from "../../../../../extensions.js";
|
||||||
@@ -10,7 +13,7 @@ import { xbLog, CacheRegistry } from "../../core/debug-core.js";
|
|||||||
import { postToIframe, isTrustedMessage } from "../../core/iframe-messaging.js";
|
import { postToIframe, isTrustedMessage } from "../../core/iframe-messaging.js";
|
||||||
import { CommonSettingStorage } from "../../core/server-storage.js";
|
import { CommonSettingStorage } from "../../core/server-storage.js";
|
||||||
|
|
||||||
// 拆分模块
|
// config/store
|
||||||
import { getSettings, getSummaryPanelConfig, getVectorConfig, saveVectorConfig } from "./data/config.js";
|
import { getSettings, getSummaryPanelConfig, getVectorConfig, saveVectorConfig } from "./data/config.js";
|
||||||
import {
|
import {
|
||||||
getSummaryStore,
|
getSummaryStore,
|
||||||
@@ -20,15 +23,17 @@ import {
|
|||||||
clearSummaryData,
|
clearSummaryData,
|
||||||
} from "./data/store.js";
|
} from "./data/store.js";
|
||||||
|
|
||||||
|
// prompt injection (ONLY on generation started)
|
||||||
import {
|
import {
|
||||||
recallAndInjectPrompt,
|
recallAndInjectPrompt,
|
||||||
updateSummaryExtensionPrompt,
|
|
||||||
clearSummaryExtensionPrompt,
|
clearSummaryExtensionPrompt,
|
||||||
|
injectNonVectorPrompt,
|
||||||
} from "./generate/prompt.js";
|
} from "./generate/prompt.js";
|
||||||
|
|
||||||
|
// summary generation
|
||||||
import { runSummaryGeneration } from "./generate/generator.js";
|
import { runSummaryGeneration } from "./generate/generator.js";
|
||||||
|
|
||||||
// 向量服务
|
// vector service
|
||||||
import {
|
import {
|
||||||
embed,
|
embed,
|
||||||
getEngineFingerprint,
|
getEngineFingerprint,
|
||||||
@@ -68,11 +73,11 @@ import {
|
|||||||
// 常量
|
// 常量
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const MODULE_ID = 'storySummary';
|
const MODULE_ID = "storySummary";
|
||||||
const SUMMARY_CONFIG_KEY = 'storySummaryPanelConfig';
|
const SUMMARY_CONFIG_KEY = "storySummaryPanelConfig";
|
||||||
const iframePath = `${extensionFolderPath}/modules/story-summary/story-summary.html`;
|
const iframePath = `${extensionFolderPath}/modules/story-summary/story-summary.html`;
|
||||||
const VALID_SECTIONS = ['keywords', 'events', 'characters', 'arcs', 'world'];
|
const VALID_SECTIONS = ["keywords", "events", "characters", "arcs", "world"];
|
||||||
const MESSAGE_EVENT = 'message';
|
const MESSAGE_EVENT = "message";
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 状态变量
|
// 状态变量
|
||||||
@@ -87,20 +92,22 @@ let eventsRegistered = false;
|
|||||||
let vectorGenerating = false;
|
let vectorGenerating = false;
|
||||||
let vectorCancelled = false;
|
let vectorCancelled = false;
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||||
// 工具函数
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// 工具:执行斜杠命令
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function executeSlashCommand(command) {
|
async function executeSlashCommand(command) {
|
||||||
try {
|
try {
|
||||||
const executeCmd = window.executeSlashCommands
|
const executeCmd =
|
||||||
|| window.executeSlashCommandsOnChatInput
|
window.executeSlashCommands ||
|
||||||
|| (typeof SillyTavern !== 'undefined' && SillyTavern.getContext()?.executeSlashCommands);
|
window.executeSlashCommandsOnChatInput ||
|
||||||
|
(typeof SillyTavern !== "undefined" && SillyTavern.getContext()?.executeSlashCommands);
|
||||||
|
|
||||||
if (executeCmd) {
|
if (executeCmd) {
|
||||||
await executeCmd(command);
|
await executeCmd(command);
|
||||||
} else if (typeof window.STscript === 'function') {
|
} else if (typeof window.STscript === "function") {
|
||||||
await window.STscript(command);
|
await window.STscript(command);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -138,17 +145,17 @@ function flushPendingFrameMessages() {
|
|||||||
if (!frameReady) return;
|
if (!frameReady) return;
|
||||||
const iframe = document.getElementById("xiaobaix-story-summary-iframe");
|
const iframe = document.getElementById("xiaobaix-story-summary-iframe");
|
||||||
if (!iframe?.contentWindow) return;
|
if (!iframe?.contentWindow) return;
|
||||||
pendingFrameMessages.forEach(p => postToIframe(iframe, p, "LittleWhiteBox"));
|
pendingFrameMessages.forEach((p) => postToIframe(iframe, p, "LittleWhiteBox"));
|
||||||
pendingFrameMessages = [];
|
pendingFrameMessages = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 向量功能
|
// 向量功能:UI 交互/状态
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
function sendVectorConfigToFrame() {
|
function sendVectorConfigToFrame() {
|
||||||
const cfg = getVectorConfig();
|
const cfg = getVectorConfig();
|
||||||
postToFrame({ type: 'VECTOR_CONFIG', config: cfg });
|
postToFrame({ type: "VECTOR_CONFIG", config: cfg });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendVectorStatsToFrame() {
|
async function sendVectorStatsToFrame() {
|
||||||
@@ -170,7 +177,7 @@ async function sendVectorStatsToFrame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: 'VECTOR_STATS',
|
type: "VECTOR_STATS",
|
||||||
stats: {
|
stats: {
|
||||||
eventCount,
|
eventCount,
|
||||||
eventVectors: stats.eventVectors,
|
eventVectors: stats.eventVectors,
|
||||||
@@ -179,7 +186,7 @@ async function sendVectorStatsToFrame() {
|
|||||||
totalFloors: chunkStatus.totalFloors,
|
totalFloors: chunkStatus.totalFloors,
|
||||||
totalMessages,
|
totalMessages,
|
||||||
},
|
},
|
||||||
mismatch
|
mismatch,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,66 +197,66 @@ async function sendLocalModelStatusToFrame(modelId) {
|
|||||||
}
|
}
|
||||||
const status = await checkLocalModelStatus(modelId);
|
const status = await checkLocalModelStatus(modelId);
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: 'VECTOR_LOCAL_MODEL_STATUS',
|
type: "VECTOR_LOCAL_MODEL_STATUS",
|
||||||
status: status.status,
|
status: status.status,
|
||||||
message: status.message
|
message: status.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownloadLocalModel(modelId) {
|
async function handleDownloadLocalModel(modelId) {
|
||||||
try {
|
try {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'downloading', message: '下载中...' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "downloading", message: "下载中..." });
|
||||||
|
|
||||||
await downloadLocalModel(modelId, (percent) => {
|
await downloadLocalModel(modelId, (percent) => {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_PROGRESS', percent });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_PROGRESS", percent });
|
||||||
});
|
});
|
||||||
|
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'ready', message: '已就绪' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "ready", message: "已就绪" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message === '下载已取消') {
|
if (e.message === "下载已取消") {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'not_downloaded', message: '已取消' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "not_downloaded", message: "已取消" });
|
||||||
} else {
|
} else {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'error', message: e.message });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "error", message: e.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancelDownload() {
|
function handleCancelDownload() {
|
||||||
cancelDownload();
|
cancelDownload();
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'not_downloaded', message: '已取消' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "not_downloaded", message: "已取消" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeleteLocalModel(modelId) {
|
async function handleDeleteLocalModel(modelId) {
|
||||||
try {
|
try {
|
||||||
await deleteLocalModelCache(modelId);
|
await deleteLocalModelCache(modelId);
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'not_downloaded', message: '未下载' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "not_downloaded", message: "未下载" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'error', message: e.message });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "error", message: e.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTestOnlineService(provider, config) {
|
async function handleTestOnlineService(provider, config) {
|
||||||
try {
|
try {
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_STATUS', status: 'downloading', message: '连接中...' });
|
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "downloading", message: "连接中..." });
|
||||||
const result = await testOnlineService(provider, config);
|
const result = await testOnlineService(provider, config);
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: 'VECTOR_ONLINE_STATUS',
|
type: "VECTOR_ONLINE_STATUS",
|
||||||
status: 'success',
|
status: "success",
|
||||||
message: `连接成功 (${result.dims}维)`
|
message: `连接成功 (${result.dims}维)`,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_STATUS', status: 'error', message: e.message });
|
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: e.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFetchOnlineModels(config) {
|
async function handleFetchOnlineModels(config) {
|
||||||
try {
|
try {
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_STATUS', status: 'downloading', message: '拉取中...' });
|
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "downloading", message: "拉取中..." });
|
||||||
const models = await fetchOnlineModels(config);
|
const models = await fetchOnlineModels(config);
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_MODELS', models });
|
postToFrame({ type: "VECTOR_ONLINE_MODELS", models });
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_STATUS', status: 'success', message: `找到 ${models.length} 个模型` });
|
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "success", message: `找到 ${models.length} 个模型` });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_STATUS', status: 'error', message: e.message });
|
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: e.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,34 +264,34 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
if (vectorGenerating) return;
|
if (vectorGenerating) return;
|
||||||
|
|
||||||
if (!vectorCfg?.enabled) {
|
if (!vectorCfg?.enabled) {
|
||||||
postToFrame({ type: 'VECTOR_GEN_PROGRESS', phase: 'L1', current: -1, total: 0 });
|
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: "L2", current: -1, total: 0 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chatId, chat } = getContext();
|
const { chatId, chat } = getContext();
|
||||||
if (!chatId || !chat?.length) return;
|
if (!chatId || !chat?.length) return;
|
||||||
|
|
||||||
if (vectorCfg.engine === 'online') {
|
if (vectorCfg.engine === "online") {
|
||||||
if (!vectorCfg.online?.key || !vectorCfg.online?.model) {
|
if (!vectorCfg.online?.key || !vectorCfg.online?.model) {
|
||||||
postToFrame({ type: 'VECTOR_ONLINE_STATUS', status: 'error', message: '请配置在线服务 API' });
|
postToFrame({ type: "VECTOR_ONLINE_STATUS", status: "error", message: "请配置在线服务 API" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vectorCfg.engine === 'local') {
|
if (vectorCfg.engine === "local") {
|
||||||
const modelId = vectorCfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
const modelId = vectorCfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||||
const status = await checkLocalModelStatus(modelId);
|
const status = await checkLocalModelStatus(modelId);
|
||||||
if (status.status !== 'ready') {
|
if (status.status !== "ready") {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'downloading', message: '正在加载模型...' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "downloading", message: "正在加载模型..." });
|
||||||
try {
|
try {
|
||||||
await downloadLocalModel(modelId, (percent) => {
|
await downloadLocalModel(modelId, (percent) => {
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_PROGRESS', percent });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_PROGRESS", percent });
|
||||||
});
|
});
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'ready', message: '已就绪' });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "ready", message: "已就绪" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
xbLog.error(MODULE_ID, '模型加载失败', e);
|
xbLog.error(MODULE_ID, "模型加载失败", e);
|
||||||
postToFrame({ type: 'VECTOR_LOCAL_MODEL_STATUS', status: 'error', message: e.message });
|
postToFrame({ type: "VECTOR_LOCAL_MODEL_STATUS", status: "error", message: e.message });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +301,7 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
vectorCancelled = false;
|
vectorCancelled = false;
|
||||||
|
|
||||||
const fingerprint = getEngineFingerprint(vectorCfg);
|
const fingerprint = getEngineFingerprint(vectorCfg);
|
||||||
const isLocal = vectorCfg.engine === 'local';
|
const isLocal = vectorCfg.engine === "local";
|
||||||
const batchSize = isLocal ? 5 : 20;
|
const batchSize = isLocal ? 5 : 20;
|
||||||
const concurrency = isLocal ? 1 : 2;
|
const concurrency = isLocal ? 1 : 2;
|
||||||
|
|
||||||
@@ -311,11 +318,11 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
await saveChunks(chatId, allChunks);
|
await saveChunks(chatId, allChunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
const l1Texts = allChunks.map(c => c.text);
|
const l1Texts = allChunks.map((c) => c.text);
|
||||||
const l1Batches = [];
|
const l1Batches = [];
|
||||||
for (let i = 0; i < l1Texts.length; i += batchSize) {
|
for (let i = 0; i < l1Texts.length; i += batchSize) {
|
||||||
l1Batches.push({
|
l1Batches.push({
|
||||||
phase: 'L1',
|
phase: "L1",
|
||||||
texts: l1Texts.slice(i, i + batchSize),
|
texts: l1Texts.slice(i, i + batchSize),
|
||||||
startIdx: i,
|
startIdx: i,
|
||||||
});
|
});
|
||||||
@@ -326,20 +333,20 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
|
|
||||||
await ensureFingerprintMatch(chatId, fingerprint);
|
await ensureFingerprintMatch(chatId, fingerprint);
|
||||||
const existingVectors = await getAllEventVectors(chatId);
|
const existingVectors = await getAllEventVectors(chatId);
|
||||||
const existingIds = new Set(existingVectors.map(v => v.eventId));
|
const existingIds = new Set(existingVectors.map((v) => v.eventId));
|
||||||
|
|
||||||
const l2Pairs = events
|
const l2Pairs = events
|
||||||
.filter(e => !existingIds.has(e.id))
|
.filter((e) => !existingIds.has(e.id))
|
||||||
.map(e => ({ id: e.id, text: `${e.title || ''} ${e.summary || ''}`.trim() }))
|
.map((e) => ({ id: e.id, text: `${e.title || ""} ${e.summary || ""}`.trim() }))
|
||||||
.filter(p => p.text);
|
.filter((p) => p.text);
|
||||||
|
|
||||||
const l2Batches = [];
|
const l2Batches = [];
|
||||||
for (let i = 0; i < l2Pairs.length; i += batchSize) {
|
for (let i = 0; i < l2Pairs.length; i += batchSize) {
|
||||||
const batch = l2Pairs.slice(i, i + batchSize);
|
const batch = l2Pairs.slice(i, i + batchSize);
|
||||||
l2Batches.push({
|
l2Batches.push({
|
||||||
phase: 'L2',
|
phase: "L2",
|
||||||
texts: batch.map(p => p.text),
|
texts: batch.map((p) => p.text),
|
||||||
ids: batch.map(p => p.id),
|
ids: batch.map((p) => p.id),
|
||||||
startIdx: i,
|
startIdx: i,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -349,8 +356,8 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
let l1Completed = 0;
|
let l1Completed = 0;
|
||||||
let l2Completed = existingIds.size;
|
let l2Completed = existingIds.size;
|
||||||
|
|
||||||
postToFrame({ type: 'VECTOR_GEN_PROGRESS', phase: 'L1', current: 0, total: l1Total });
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L1", current: 0, total: l1Total });
|
||||||
postToFrame({ type: 'VECTOR_GEN_PROGRESS', phase: 'L2', current: l2Completed, total: l2Total });
|
postToFrame({ type: "VECTOR_GEN_PROGRESS", phase: "L2", current: l2Completed, total: l2Total });
|
||||||
|
|
||||||
const allTasks = [...l1Batches, ...l2Batches];
|
const allTasks = [...l1Batches, ...l2Batches];
|
||||||
const l1Vectors = new Array(l1Texts.length);
|
const l1Vectors = new Array(l1Texts.length);
|
||||||
@@ -370,14 +377,14 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
try {
|
try {
|
||||||
const vectors = await embed(task.texts, vectorCfg);
|
const vectors = await embed(task.texts, vectorCfg);
|
||||||
|
|
||||||
if (task.phase === 'L1') {
|
if (task.phase === "L1") {
|
||||||
for (let j = 0; j < vectors.length; j++) {
|
for (let j = 0; j < vectors.length; j++) {
|
||||||
l1Vectors[task.startIdx + j] = vectors[j];
|
l1Vectors[task.startIdx + j] = vectors[j];
|
||||||
}
|
}
|
||||||
l1Completed += task.texts.length;
|
l1Completed += task.texts.length;
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: 'VECTOR_GEN_PROGRESS',
|
type: "VECTOR_GEN_PROGRESS",
|
||||||
phase: 'L1',
|
phase: "L1",
|
||||||
current: Math.min(l1Completed, l1Total),
|
current: Math.min(l1Completed, l1Total),
|
||||||
total: l1Total,
|
total: l1Total,
|
||||||
});
|
});
|
||||||
@@ -387,8 +394,8 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
}
|
}
|
||||||
l2Completed += task.texts.length;
|
l2Completed += task.texts.length;
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: 'VECTOR_GEN_PROGRESS',
|
type: "VECTOR_GEN_PROGRESS",
|
||||||
phase: 'L2',
|
phase: "L2",
|
||||||
current: Math.min(l2Completed, l2Total),
|
current: Math.min(l2Completed, l2Total),
|
||||||
total: l2Total,
|
total: l2Total,
|
||||||
});
|
});
|
||||||
@@ -407,7 +414,7 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
|
|
||||||
if (allChunks.length > 0 && l1Vectors.filter(Boolean).length > 0) {
|
if (allChunks.length > 0 && l1Vectors.filter(Boolean).length > 0) {
|
||||||
const chunkVectorItems = allChunks
|
const chunkVectorItems = allChunks
|
||||||
.map((chunk, idx) => l1Vectors[idx] ? { chunkId: chunk.chunkId, vector: l1Vectors[idx] } : null)
|
.map((chunk, idx) => (l1Vectors[idx] ? { chunkId: chunk.chunkId, vector: l1Vectors[idx] } : null))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
await saveChunkVectors(chatId, chunkVectorItems, fingerprint);
|
await saveChunkVectors(chatId, chunkVectorItems, fingerprint);
|
||||||
await updateMeta(chatId, { lastChunkFloor: chat.length - 1 });
|
await updateMeta(chatId, { lastChunkFloor: chat.length - 1 });
|
||||||
@@ -417,8 +424,8 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
await saveEventVectorsToDb(chatId, l2VectorItems, fingerprint);
|
await saveEventVectorsToDb(chatId, l2VectorItems, fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
postToFrame({ type: 'VECTOR_GEN_PROGRESS', phase: 'L1', current: -1, total: 0 });
|
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: "L2", current: -1, total: 0 });
|
||||||
await sendVectorStatsToFrame();
|
await sendVectorStatsToFrame();
|
||||||
|
|
||||||
vectorGenerating = false;
|
vectorGenerating = false;
|
||||||
@@ -435,61 +442,30 @@ async function handleClearVectors() {
|
|||||||
await clearAllChunks(chatId);
|
await clearAllChunks(chatId);
|
||||||
await updateMeta(chatId, { lastChunkFloor: -1 });
|
await updateMeta(chatId, { lastChunkFloor: -1 });
|
||||||
await sendVectorStatsToFrame();
|
await sendVectorStatsToFrame();
|
||||||
xbLog.info(MODULE_ID, '向量数据已清除');
|
xbLog.info(MODULE_ID, "向量数据已清除");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoVectorizeNewEvents(newEventIds) {
|
async function maybeAutoBuildChunks() {
|
||||||
const cfg = getVectorConfig();
|
const cfg = getVectorConfig();
|
||||||
if (!cfg?.enabled || !newEventIds?.length) return;
|
if (!cfg?.enabled) return;
|
||||||
|
|
||||||
await sleep(3000);
|
const { chat, chatId } = getContext();
|
||||||
|
if (!chatId || !chat?.length) return;
|
||||||
|
|
||||||
const { chatId } = getContext();
|
const status = await getChunkBuildStatus();
|
||||||
if (!chatId) return;
|
if (status.pending <= 0) return;
|
||||||
|
|
||||||
const store = getSummaryStore();
|
if (cfg.engine === "local") {
|
||||||
const events = store?.json?.events || [];
|
|
||||||
|
|
||||||
const fingerprint = getEngineFingerprint(cfg);
|
|
||||||
const meta = await getMeta(chatId);
|
|
||||||
if (meta.fingerprint && meta.fingerprint !== fingerprint) {
|
|
||||||
xbLog.warn(MODULE_ID, '引擎不匹配,跳过自动向量化');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cfg.engine === 'local') {
|
|
||||||
const modelId = cfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
const modelId = cfg.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||||
if (!isLocalModelLoaded(modelId)) {
|
if (!isLocalModelLoaded(modelId)) return;
|
||||||
xbLog.warn(MODULE_ID, '本地模型未加载,跳过自动向量化');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toVectorize = newEventIds.filter(id => events.some(e => e.id === id));
|
xbLog.info(MODULE_ID, `auto L1 chunks: pending=${status.pending}`);
|
||||||
if (toVectorize.length === 0) return;
|
|
||||||
|
|
||||||
const texts = toVectorize.map(id => {
|
|
||||||
const event = events.find(e => e.id === id);
|
|
||||||
return event ? `${event.title || ''} ${event.summary || ''}`.trim() : '';
|
|
||||||
}).filter(t => t);
|
|
||||||
|
|
||||||
if (!texts.length) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const vectors = await embed(texts, cfg);
|
await buildIncrementalChunks({ vectorConfig: cfg });
|
||||||
const newVectorItems = [];
|
|
||||||
for (let i = 0; i < toVectorize.length && i < vectors.length; i++) {
|
|
||||||
newVectorItems.push({
|
|
||||||
eventId: toVectorize[i],
|
|
||||||
vector: vectors[i],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (newVectorItems.length > 0) {
|
|
||||||
await saveEventVectorsToDb(chatId, newVectorItems, fingerprint);
|
|
||||||
}
|
|
||||||
xbLog.info(MODULE_ID, `自动向量化 ${toVectorize.length} 个新事件`);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
xbLog.error(MODULE_ID, '自动向量化失败', e);
|
xbLog.error(MODULE_ID, "自动 L1 构建失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,8 +478,8 @@ function createOverlay() {
|
|||||||
overlayCreated = true;
|
overlayCreated = true;
|
||||||
|
|
||||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(navigator.userAgent);
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(navigator.userAgent);
|
||||||
const isNarrow = window.matchMedia?.('(max-width: 768px)').matches;
|
const isNarrow = window.matchMedia?.("(max-width: 768px)").matches;
|
||||||
const overlayHeight = (isMobile || isNarrow) ? '92.5vh' : '100vh';
|
const overlayHeight = (isMobile || isNarrow) ? "92.5vh" : "100vh";
|
||||||
|
|
||||||
const $overlay = $(`
|
const $overlay = $(`
|
||||||
<div id="xiaobaix-story-summary-overlay" style="
|
<div id="xiaobaix-story-summary-overlay" style="
|
||||||
@@ -557,12 +533,12 @@ function hideOverlay() {
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
function createSummaryBtn(mesId) {
|
function createSummaryBtn(mesId) {
|
||||||
const btn = document.createElement('div');
|
const btn = document.createElement("div");
|
||||||
btn.className = 'mes_btn xiaobaix-story-summary-btn';
|
btn.className = "mes_btn xiaobaix-story-summary-btn";
|
||||||
btn.title = '剧情总结';
|
btn.title = "剧情总结";
|
||||||
btn.dataset.mesid = mesId;
|
btn.dataset.mesid = mesId;
|
||||||
btn.innerHTML = '<i class="fa-solid fa-chart-line"></i>';
|
btn.innerHTML = '<i class="fa-solid fa-chart-line"></i>';
|
||||||
btn.addEventListener('click', e => {
|
btn.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!getSettings().storySummary?.enabled) return;
|
if (!getSettings().storySummary?.enabled) return;
|
||||||
@@ -575,10 +551,12 @@ function createSummaryBtn(mesId) {
|
|||||||
function addSummaryBtnToMessage(mesId) {
|
function addSummaryBtnToMessage(mesId) {
|
||||||
if (!getSettings().storySummary?.enabled) return;
|
if (!getSettings().storySummary?.enabled) return;
|
||||||
const msg = document.querySelector(`#chat .mes[mesid="${mesId}"]`);
|
const msg = document.querySelector(`#chat .mes[mesid="${mesId}"]`);
|
||||||
if (!msg || msg.querySelector('.xiaobaix-story-summary-btn')) return;
|
if (!msg || msg.querySelector(".xiaobaix-story-summary-btn")) return;
|
||||||
|
|
||||||
const btn = createSummaryBtn(mesId);
|
const btn = createSummaryBtn(mesId);
|
||||||
if (window.registerButtonToSubContainer?.(mesId, btn)) return;
|
if (window.registerButtonToSubContainer?.(mesId, btn)) return;
|
||||||
msg.querySelector('.flex-container.flex1.alignitemscenter')?.appendChild(btn);
|
|
||||||
|
msg.querySelector(".flex-container.flex1.alignitemscenter")?.appendChild(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initButtonsForAll() {
|
function initButtonsForAll() {
|
||||||
@@ -598,10 +576,10 @@ async function sendSavedConfigToFrame() {
|
|||||||
const savedConfig = await CommonSettingStorage.get(SUMMARY_CONFIG_KEY, null);
|
const savedConfig = await CommonSettingStorage.get(SUMMARY_CONFIG_KEY, null);
|
||||||
if (savedConfig) {
|
if (savedConfig) {
|
||||||
postToFrame({ type: "LOAD_PANEL_CONFIG", config: savedConfig });
|
postToFrame({ type: "LOAD_PANEL_CONFIG", config: savedConfig });
|
||||||
xbLog.info(MODULE_ID, '已从服务器加载面板配置');
|
xbLog.info(MODULE_ID, "已从服务器加载面板配置");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
xbLog.warn(MODULE_ID, '加载面板配置失败', e);
|
xbLog.warn(MODULE_ID, "加载面板配置失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,54 +624,25 @@ function sendFrameFullData(store, totalFloors) {
|
|||||||
function openPanelForMessage(mesId) {
|
function openPanelForMessage(mesId) {
|
||||||
createOverlay();
|
createOverlay();
|
||||||
showOverlay();
|
showOverlay();
|
||||||
|
|
||||||
const { chat } = getContext();
|
const { chat } = getContext();
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
const totalFloors = chat.length;
|
const totalFloors = chat.length;
|
||||||
|
|
||||||
sendFrameBaseData(store, totalFloors);
|
sendFrameBaseData(store, totalFloors);
|
||||||
sendFrameFullData(store, totalFloors);
|
sendFrameFullData(store, totalFloors);
|
||||||
setSummaryGenerating(summaryGenerating);
|
setSummaryGenerating(summaryGenerating);
|
||||||
|
|
||||||
sendVectorConfigToFrame();
|
sendVectorConfigToFrame();
|
||||||
sendVectorStatsToFrame();
|
sendVectorStatsToFrame();
|
||||||
|
|
||||||
const cfg = getVectorConfig();
|
const cfg = getVectorConfig();
|
||||||
const modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
const modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||||
sendLocalModelStatusToFrame(modelId);
|
sendLocalModelStatusToFrame(modelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyFrameAfterRollback(store) {
|
|
||||||
const { chat } = getContext();
|
|
||||||
const totalFloors = Array.isArray(chat) ? chat.length : 0;
|
|
||||||
const lastSummarized = store.lastSummarizedMesId ?? -1;
|
|
||||||
|
|
||||||
if (store.json) {
|
|
||||||
postToFrame({
|
|
||||||
type: "SUMMARY_FULL_DATA",
|
|
||||||
payload: {
|
|
||||||
keywords: store.json.keywords || [],
|
|
||||||
events: store.json.events || [],
|
|
||||||
characters: store.json.characters || { main: [], relationships: [] },
|
|
||||||
arcs: store.json.arcs || [],
|
|
||||||
world: store.json.world || [],
|
|
||||||
lastSummarizedMesId: lastSummarized,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
postToFrame({ type: "SUMMARY_CLEARED", payload: { totalFloors } });
|
|
||||||
}
|
|
||||||
|
|
||||||
postToFrame({
|
|
||||||
type: "SUMMARY_BASE_DATA",
|
|
||||||
stats: {
|
|
||||||
totalFloors,
|
|
||||||
summarizedUpTo: lastSummarized + 1,
|
|
||||||
eventsCount: store.json?.events?.length || 0,
|
|
||||||
pendingFloors: totalFloors - lastSummarized - 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 自动触发总结
|
// 自动总结(保持原逻辑;不做 prompt 注入)
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function maybeAutoRunSummary(reason) {
|
async function maybeAutoRunSummary(reason) {
|
||||||
@@ -704,10 +653,10 @@ async function maybeAutoRunSummary(reason) {
|
|||||||
const cfgAll = getSummaryPanelConfig();
|
const cfgAll = getSummaryPanelConfig();
|
||||||
const trig = cfgAll.trigger || {};
|
const trig = cfgAll.trigger || {};
|
||||||
|
|
||||||
if (trig.timing === 'manual') return;
|
if (trig.timing === "manual") return;
|
||||||
if (!trig.enabled) return;
|
if (!trig.enabled) return;
|
||||||
if (trig.timing === 'after_ai' && reason !== 'after_ai') return;
|
if (trig.timing === "after_ai" && reason !== "after_ai") return;
|
||||||
if (trig.timing === 'before_user' && reason !== 'before_user') return;
|
if (trig.timing === "before_user" && reason !== "before_user") return;
|
||||||
|
|
||||||
if (isSummaryGenerating()) return;
|
if (isSummaryGenerating()) return;
|
||||||
|
|
||||||
@@ -720,30 +669,6 @@ async function maybeAutoRunSummary(reason) {
|
|||||||
await autoRunSummaryWithRetry(chat.length - 1, { api: cfgAll.api, gen: cfgAll.gen, trigger: trig });
|
await autoRunSummaryWithRetry(chat.length - 1, { api: cfgAll.api, gen: cfgAll.gen, trigger: trig });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function maybeAutoBuildChunks() {
|
|
||||||
const cfg = getVectorConfig();
|
|
||||||
if (!cfg?.enabled) return;
|
|
||||||
|
|
||||||
const { chat, chatId } = getContext();
|
|
||||||
if (!chatId || !chat?.length) return;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
xbLog.info(MODULE_ID, `auto L1 chunks: pending=${status.pending}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await buildIncrementalChunks({ vectorConfig: cfg });
|
|
||||||
} catch (e) {
|
|
||||||
xbLog.error(MODULE_ID, '自动 L1 构建失败', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
||||||
setSummaryGenerating(true);
|
setSummaryGenerating(true);
|
||||||
|
|
||||||
@@ -751,7 +676,7 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
|||||||
const result = await runSummaryGeneration(targetMesId, configForRun, {
|
const result = await runSummaryGeneration(targetMesId, configForRun, {
|
||||||
onStatus: (text) => postToFrame({ type: "SUMMARY_STATUS", statusText: text }),
|
onStatus: (text) => postToFrame({ type: "SUMMARY_STATUS", statusText: text }),
|
||||||
onError: (msg) => postToFrame({ type: "SUMMARY_ERROR", message: msg }),
|
onError: (msg) => postToFrame({ type: "SUMMARY_ERROR", message: msg }),
|
||||||
onComplete: ({ merged, endMesId, newEventIds }) => {
|
onComplete: ({ merged, endMesId }) => {
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: "SUMMARY_FULL_DATA",
|
type: "SUMMARY_FULL_DATA",
|
||||||
payload: {
|
payload: {
|
||||||
@@ -759,7 +684,7 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
|||||||
events: merged.events || [],
|
events: merged.events || [],
|
||||||
characters: merged.characters || { main: [], relationships: [] },
|
characters: merged.characters || { main: [], relationships: [] },
|
||||||
arcs: merged.arcs || [],
|
arcs: merged.arcs || [],
|
||||||
world: merged.world || [],
|
world: merged.world || [],
|
||||||
lastSummarizedMesId: endMesId,
|
lastSummarizedMesId: endMesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -768,8 +693,6 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
|||||||
statusText: `已更新至 ${endMesId + 1} 楼 · ${merged.events?.length || 0} 个事件`,
|
statusText: `已更新至 ${endMesId + 1} 楼 · ${merged.events?.length || 0} 个事件`,
|
||||||
});
|
});
|
||||||
updateFrameStatsAfterSummary(endMesId, merged);
|
updateFrameStatsAfterSummary(endMesId, merged);
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
autoVectorizeNewEvents(newEventIds);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -782,8 +705,8 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSummaryGenerating(false);
|
setSummaryGenerating(false);
|
||||||
xbLog.error(MODULE_ID, '自动总结失败(已重试3次)');
|
xbLog.error(MODULE_ID, "自动总结失败(已重试3次)");
|
||||||
await executeSlashCommand('/echo severity=error 剧情总结失败(已自动重试 3 次)。请稍后再试。');
|
await executeSlashCommand("/echo severity=error 剧情总结失败(已自动重试 3 次)。请稍后再试。");
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFrameStatsAfterSummary(endMesId, merged) {
|
function updateFrameStatsAfterSummary(endMesId, merged) {
|
||||||
@@ -812,22 +735,23 @@ function updateFrameStatsAfterSummary(endMesId, merged) {
|
|||||||
function handleFrameMessage(event) {
|
function handleFrameMessage(event) {
|
||||||
const iframe = document.getElementById("xiaobaix-story-summary-iframe");
|
const iframe = document.getElementById("xiaobaix-story-summary-iframe");
|
||||||
if (!isTrustedMessage(event, iframe, "LittleWhiteBox-StoryFrame")) return;
|
if (!isTrustedMessage(event, iframe, "LittleWhiteBox-StoryFrame")) return;
|
||||||
|
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "FRAME_READY":
|
case "FRAME_READY": {
|
||||||
frameReady = true;
|
frameReady = true;
|
||||||
flushPendingFrameMessages();
|
flushPendingFrameMessages();
|
||||||
setSummaryGenerating(summaryGenerating);
|
setSummaryGenerating(summaryGenerating);
|
||||||
sendSavedConfigToFrame();
|
sendSavedConfigToFrame();
|
||||||
sendVectorConfigToFrame();
|
sendVectorConfigToFrame();
|
||||||
sendVectorStatsToFrame();
|
sendVectorStatsToFrame();
|
||||||
{
|
|
||||||
const cfg = getVectorConfig();
|
const cfg = getVectorConfig();
|
||||||
const modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
const modelId = cfg?.local?.modelId || DEFAULT_LOCAL_MODEL;
|
||||||
sendLocalModelStatusToFrame(modelId);
|
sendLocalModelStatusToFrame(modelId);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "SETTINGS_OPENED":
|
case "SETTINGS_OPENED":
|
||||||
case "FULLSCREEN_OPENED":
|
case "FULLSCREEN_OPENED":
|
||||||
@@ -849,11 +773,12 @@ function handleFrameMessage(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "REQUEST_CANCEL":
|
case "REQUEST_CANCEL":
|
||||||
window.xiaobaixStreamingGeneration?.cancel?.('xb9');
|
window.xiaobaixStreamingGeneration?.cancel?.("xb9");
|
||||||
setSummaryGenerating(false);
|
setSummaryGenerating(false);
|
||||||
postToFrame({ type: "SUMMARY_STATUS", statusText: "已停止" });
|
postToFrame({ type: "SUMMARY_STATUS", statusText: "已停止" });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// vector UI
|
||||||
case "VECTOR_DOWNLOAD_MODEL":
|
case "VECTOR_DOWNLOAD_MODEL":
|
||||||
handleDownloadLocalModel(data.modelId);
|
handleDownloadLocalModel(data.modelId);
|
||||||
break;
|
break;
|
||||||
@@ -880,10 +805,12 @@ function handleFrameMessage(event) {
|
|||||||
|
|
||||||
case "VECTOR_GENERATE":
|
case "VECTOR_GENERATE":
|
||||||
if (data.config) saveVectorConfig(data.config);
|
if (data.config) saveVectorConfig(data.config);
|
||||||
|
clearSummaryExtensionPrompt(); // 防残留
|
||||||
handleGenerateVectors(data.config);
|
handleGenerateVectors(data.config);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "VECTOR_CLEAR":
|
case "VECTOR_CLEAR":
|
||||||
|
clearSummaryExtensionPrompt(); // 防残留
|
||||||
handleClearVectors();
|
handleClearVectors();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -891,10 +818,11 @@ function handleFrameMessage(event) {
|
|||||||
vectorCancelled = true;
|
vectorCancelled = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// summary actions
|
||||||
case "REQUEST_CLEAR": {
|
case "REQUEST_CLEAR": {
|
||||||
const { chat, chatId } = getContext();
|
const { chat, chatId } = getContext();
|
||||||
clearSummaryData(chatId);
|
clearSummaryData(chatId);
|
||||||
clearSummaryExtensionPrompt();
|
clearSummaryExtensionPrompt(); // 防残留
|
||||||
postToFrame({
|
postToFrame({
|
||||||
type: "SUMMARY_CLEARED",
|
type: "SUMMARY_CLEARED",
|
||||||
payload: { totalFloors: Array.isArray(chat) ? chat.length : 0 },
|
payload: { totalFloors: Array.isArray(chat) ? chat.length : 0 },
|
||||||
@@ -915,7 +843,6 @@ function handleFrameMessage(event) {
|
|||||||
}
|
}
|
||||||
store.updatedAt = Date.now();
|
store.updatedAt = Date.now();
|
||||||
saveSummaryStore();
|
saveSummaryStore();
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,8 +851,10 @@ function handleFrameMessage(event) {
|
|||||||
if (!store) break;
|
if (!store) break;
|
||||||
const lastSummarized = store.lastSummarizedMesId ?? -1;
|
const lastSummarized = store.lastSummarizedMesId ?? -1;
|
||||||
if (lastSummarized < 0) break;
|
if (lastSummarized < 0) break;
|
||||||
|
|
||||||
store.hideSummarizedHistory = !!data.enabled;
|
store.hideSummarizedHistory = !!data.enabled;
|
||||||
saveSummaryStore();
|
saveSummaryStore();
|
||||||
|
|
||||||
if (data.enabled) {
|
if (data.enabled) {
|
||||||
const range = calcHideRange(lastSummarized);
|
const range = calcHideRange(lastSummarized);
|
||||||
if (range) executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
if (range) executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
||||||
@@ -953,9 +882,7 @@ function handleFrameMessage(event) {
|
|||||||
(async () => {
|
(async () => {
|
||||||
await executeSlashCommand(`/unhide 0-${lastSummarized}`);
|
await executeSlashCommand(`/unhide 0-${lastSummarized}`);
|
||||||
const range = calcHideRange(lastSummarized);
|
const range = calcHideRange(lastSummarized);
|
||||||
if (range) {
|
if (range) await executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
||||||
await executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
|
||||||
}
|
|
||||||
const { chat } = getContext();
|
const { chat } = getContext();
|
||||||
sendFrameBaseData(store, Array.isArray(chat) ? chat.length : 0);
|
sendFrameBaseData(store, Array.isArray(chat) ? chat.length : 0);
|
||||||
})();
|
})();
|
||||||
@@ -969,7 +896,8 @@ function handleFrameMessage(event) {
|
|||||||
case "SAVE_PANEL_CONFIG":
|
case "SAVE_PANEL_CONFIG":
|
||||||
if (data.config) {
|
if (data.config) {
|
||||||
CommonSettingStorage.set(SUMMARY_CONFIG_KEY, data.config);
|
CommonSettingStorage.set(SUMMARY_CONFIG_KEY, data.config);
|
||||||
xbLog.info(MODULE_ID, '面板配置已保存到服务器');
|
clearSummaryExtensionPrompt(); // 配置变化立即清除注入,避免残留
|
||||||
|
xbLog.info(MODULE_ID, "面板配置已保存到服务器");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -979,6 +907,10 @@ function handleFrameMessage(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// 手动总结
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function handleManualGenerate(mesId, config) {
|
async function handleManualGenerate(mesId, config) {
|
||||||
if (isSummaryGenerating()) {
|
if (isSummaryGenerating()) {
|
||||||
postToFrame({ type: "SUMMARY_STATUS", statusText: "上一轮总结仍在进行中..." });
|
postToFrame({ type: "SUMMARY_STATUS", statusText: "上一轮总结仍在进行中..." });
|
||||||
@@ -991,7 +923,7 @@ async function handleManualGenerate(mesId, config) {
|
|||||||
await runSummaryGeneration(mesId, config, {
|
await runSummaryGeneration(mesId, config, {
|
||||||
onStatus: (text) => postToFrame({ type: "SUMMARY_STATUS", statusText: text }),
|
onStatus: (text) => postToFrame({ type: "SUMMARY_STATUS", statusText: text }),
|
||||||
onError: (msg) => postToFrame({ type: "SUMMARY_ERROR", message: msg }),
|
onError: (msg) => postToFrame({ type: "SUMMARY_ERROR", message: msg }),
|
||||||
onComplete: ({ merged, endMesId, newEventIds }) => {
|
onComplete: ({ merged, endMesId }) => {
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
|
|
||||||
postToFrame({
|
postToFrame({
|
||||||
@@ -1011,18 +943,14 @@ async function handleManualGenerate(mesId, config) {
|
|||||||
statusText: `已更新至 ${endMesId + 1} 楼 · ${merged.events?.length || 0} 个事件`,
|
statusText: `已更新至 ${endMesId + 1} 楼 · ${merged.events?.length || 0} 个事件`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理隐藏逻辑
|
// 隐藏逻辑(与注入无关)
|
||||||
const lastSummarized = store?.lastSummarizedMesId ?? -1;
|
const lastSummarized = store?.lastSummarizedMesId ?? -1;
|
||||||
if (store?.hideSummarizedHistory && lastSummarized >= 0) {
|
if (store?.hideSummarizedHistory && lastSummarized >= 0) {
|
||||||
const range = calcHideRange(lastSummarized);
|
const range = calcHideRange(lastSummarized);
|
||||||
if (range) {
|
if (range) executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
||||||
executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFrameStatsAfterSummary(endMesId, merged);
|
updateFrameStatsAfterSummary(endMesId, merged);
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
autoVectorizeNewEvents(newEventIds);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1030,7 +958,7 @@ async function handleManualGenerate(mesId, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 事件处理器
|
// 事件处理器(不做 prompt 注入)
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function handleChatChanged() {
|
async function handleChatChanged() {
|
||||||
@@ -1039,7 +967,6 @@ async function handleChatChanged() {
|
|||||||
|
|
||||||
await rollbackSummaryIfNeeded();
|
await rollbackSummaryIfNeeded();
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
|
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
const lastSummarized = store?.lastSummarizedMesId ?? -1;
|
const lastSummarized = store?.lastSummarizedMesId ?? -1;
|
||||||
@@ -1052,7 +979,6 @@ async function handleChatChanged() {
|
|||||||
if (frameReady) {
|
if (frameReady) {
|
||||||
sendFrameBaseData(store, newLength);
|
sendFrameBaseData(store, newLength);
|
||||||
sendFrameFullData(store, newLength);
|
sendFrameFullData(store, newLength);
|
||||||
notifyFrameAfterRollback(store);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1061,9 +987,6 @@ async function handleMessageDeleted() {
|
|||||||
const newLength = chat?.length || 0;
|
const newLength = chat?.length || 0;
|
||||||
|
|
||||||
await rollbackSummaryIfNeeded();
|
await rollbackSummaryIfNeeded();
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
|
|
||||||
// L1 同步
|
|
||||||
await syncOnMessageDeleted(chatId, newLength);
|
await syncOnMessageDeleted(chatId, newLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1071,10 +994,7 @@ async function handleMessageSwiped() {
|
|||||||
const { chat, chatId } = getContext();
|
const { chat, chatId } = getContext();
|
||||||
const lastFloor = (chat?.length || 1) - 1;
|
const lastFloor = (chat?.length || 1) - 1;
|
||||||
|
|
||||||
// L1 同步
|
|
||||||
await syncOnMessageSwiped(chatId, lastFloor);
|
await syncOnMessageSwiped(chatId, lastFloor);
|
||||||
|
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,41 +1004,52 @@ async function handleMessageReceived() {
|
|||||||
const message = chat?.[lastFloor];
|
const message = chat?.[lastFloor];
|
||||||
const vectorConfig = getVectorConfig();
|
const vectorConfig = getVectorConfig();
|
||||||
|
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
|
|
||||||
// L1 同步
|
|
||||||
await syncOnMessageReceived(chatId, lastFloor, message, vectorConfig);
|
await syncOnMessageReceived(chatId, lastFloor, message, vectorConfig);
|
||||||
await maybeAutoBuildChunks();
|
await maybeAutoBuildChunks();
|
||||||
|
|
||||||
setTimeout(() => maybeAutoRunSummary('after_ai'), 1000);
|
setTimeout(() => maybeAutoRunSummary("after_ai"), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessageSent() {
|
function handleMessageSent() {
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
setTimeout(() => maybeAutoRunSummary('before_user'), 1000);
|
setTimeout(() => maybeAutoRunSummary("before_user"), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMessageUpdated() {
|
async function handleMessageUpdated() {
|
||||||
await rollbackSummaryIfNeeded();
|
await rollbackSummaryIfNeeded();
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessageRendered(data) {
|
function handleMessageRendered(data) {
|
||||||
const mesId = data?.element ? $(data.element).attr("mesid") : data?.messageId;
|
const mesId = data?.element ? $(data.element).attr("mesid") : data?.messageId;
|
||||||
if (mesId != null) {
|
if (mesId != null) addSummaryBtnToMessage(mesId);
|
||||||
addSummaryBtnToMessage(mesId);
|
else initButtonsForAll();
|
||||||
} else {
|
|
||||||
initButtonsForAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// ✅ 唯一注入入口:GENERATION_STARTED
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function handleGenerationStarted(type, _params, isDryRun) {
|
async function handleGenerationStarted(type, _params, isDryRun) {
|
||||||
if (isDryRun) return;
|
if (isDryRun) return;
|
||||||
const excludeLastAi = type === 'swipe' || type === 'regenerate';
|
if (!getSettings().storySummary?.enabled) return;
|
||||||
await recallAndInjectPrompt(excludeLastAi, postToFrame);
|
|
||||||
|
const excludeLastAi = type === "swipe" || type === "regenerate";
|
||||||
|
const vectorCfg = getVectorConfig();
|
||||||
|
|
||||||
|
// 向量模式:召回 + 预算装配
|
||||||
|
if (vectorCfg?.enabled) {
|
||||||
|
await recallAndInjectPrompt(excludeLastAi, {
|
||||||
|
postToFrame,
|
||||||
|
echo: executeSlashCommand, // recall failure notification
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非向量模式:全量总结注入(不召回)
|
||||||
|
await injectNonVectorPrompt(postToFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -1129,14 +1060,17 @@ function registerEvents() {
|
|||||||
if (eventsRegistered) return;
|
if (eventsRegistered) return;
|
||||||
eventsRegistered = true;
|
eventsRegistered = true;
|
||||||
|
|
||||||
xbLog.info(MODULE_ID, '模块初始化');
|
xbLog.info(MODULE_ID, "模块初始化");
|
||||||
|
|
||||||
CacheRegistry.register(MODULE_ID, {
|
CacheRegistry.register(MODULE_ID, {
|
||||||
name: '待发送消息队列',
|
name: "待发送消息队列",
|
||||||
getSize: () => pendingFrameMessages.length,
|
getSize: () => pendingFrameMessages.length,
|
||||||
getBytes: () => {
|
getBytes: () => {
|
||||||
try { return JSON.stringify(pendingFrameMessages || []).length * 2; }
|
try {
|
||||||
catch { return 0; }
|
return JSON.stringify(pendingFrameMessages || []).length * 2;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clear: () => {
|
clear: () => {
|
||||||
pendingFrameMessages = [];
|
pendingFrameMessages = [];
|
||||||
@@ -1153,17 +1087,22 @@ function registerEvents() {
|
|||||||
eventSource.on(event_types.MESSAGE_SWIPED, () => setTimeout(handleMessageSwiped, 100));
|
eventSource.on(event_types.MESSAGE_SWIPED, () => setTimeout(handleMessageSwiped, 100));
|
||||||
eventSource.on(event_types.MESSAGE_UPDATED, () => setTimeout(handleMessageUpdated, 100));
|
eventSource.on(event_types.MESSAGE_UPDATED, () => setTimeout(handleMessageUpdated, 100));
|
||||||
eventSource.on(event_types.MESSAGE_EDITED, () => setTimeout(handleMessageUpdated, 100));
|
eventSource.on(event_types.MESSAGE_EDITED, () => setTimeout(handleMessageUpdated, 100));
|
||||||
eventSource.on(event_types.USER_MESSAGE_RENDERED, data => setTimeout(() => handleMessageRendered(data), 50));
|
eventSource.on(event_types.USER_MESSAGE_RENDERED, (data) => setTimeout(() => handleMessageRendered(data), 50));
|
||||||
eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, data => setTimeout(() => handleMessageRendered(data), 50));
|
eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (data) => setTimeout(() => handleMessageRendered(data), 50));
|
||||||
|
|
||||||
|
// ✅ 只在生成开始时注入
|
||||||
eventSource.on(event_types.GENERATION_STARTED, handleGenerationStarted);
|
eventSource.on(event_types.GENERATION_STARTED, handleGenerationStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisterEvents() {
|
function unregisterEvents() {
|
||||||
xbLog.info(MODULE_ID, '模块清理');
|
xbLog.info(MODULE_ID, "模块清理");
|
||||||
CacheRegistry.unregister(MODULE_ID);
|
CacheRegistry.unregister(MODULE_ID);
|
||||||
eventsRegistered = false;
|
eventsRegistered = false;
|
||||||
|
|
||||||
$(".xiaobaix-story-summary-btn").remove();
|
$(".xiaobaix-story-summary-btn").remove();
|
||||||
hideOverlay();
|
hideOverlay();
|
||||||
|
|
||||||
|
// 禁用时清理注入,避免残留
|
||||||
clearSummaryExtensionPrompt();
|
clearSummaryExtensionPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1175,7 +1114,9 @@ $(document).on("xiaobaix:storySummary:toggle", (_e, enabled) => {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
registerEvents();
|
registerEvents();
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
|
// 开启时清一次,防止旧注入残留
|
||||||
|
clearSummaryExtensionPrompt();
|
||||||
} else {
|
} else {
|
||||||
unregisterEvents();
|
unregisterEvents();
|
||||||
}
|
}
|
||||||
@@ -1191,5 +1132,7 @@ jQuery(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
registerEvents();
|
registerEvents();
|
||||||
updateSummaryExtensionPrompt();
|
|
||||||
|
// 初始化也清一次,保证干净(注入只在生成开始发生)
|
||||||
|
clearSummaryExtensionPrompt();
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user