Fix vector recall pending user message
This commit is contained in:
@@ -1,17 +1,10 @@
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// Story Summary - Prompt Injection (Final Clean Version)
|
// Story Summary - Prompt Injection (Final Clean Version)
|
||||||
// - 注入只在 GENERATION_STARTED 发生(由 story-summary.js 调用)
|
// - 仅负责“构建注入文本”,不负责写入 extension_prompts
|
||||||
// - 向量关闭:注入全量总结(世界/事件/弧光)
|
// - 注入发生在 story-summary.js 的 CHAT_COMPLETION_PROMPT_READY(最终 messages 已裁剪)
|
||||||
// - 向量开启:召回 + 预算装配注入
|
|
||||||
// - 没有“快速注入”写入 extension_prompts,避免覆盖/残留/竞态
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
import { getContext } from "../../../../../../extensions.js";
|
import { getContext } from "../../../../../../extensions.js";
|
||||||
import {
|
|
||||||
extension_prompts,
|
|
||||||
extension_prompt_types,
|
|
||||||
extension_prompt_roles,
|
|
||||||
} from "../../../../../../../script.js";
|
|
||||||
import { xbLog } from "../../../core/debug-core.js";
|
import { xbLog } from "../../../core/debug-core.js";
|
||||||
import { getSummaryStore } from "../data/store.js";
|
import { getSummaryStore } from "../data/store.js";
|
||||||
import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js";
|
import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js";
|
||||||
@@ -19,7 +12,6 @@ import { recallMemory, buildQueryText } from "../vector/recall.js";
|
|||||||
import { getChunksByFloors, getAllChunkVectors, getAllEventVectors, getMeta } from "../vector/chunk-store.js";
|
import { getChunksByFloors, getAllChunkVectors, getAllEventVectors, getMeta } from "../vector/chunk-store.js";
|
||||||
|
|
||||||
const MODULE_ID = "summaryPrompt";
|
const MODULE_ID = "summaryPrompt";
|
||||||
const SUMMARY_PROMPT_KEY = "LittleWhiteBox_StorySummary";
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// 召回失败提示节流(避免连续生成刷屏)
|
// 召回失败提示节流(避免连续生成刷屏)
|
||||||
@@ -137,7 +129,8 @@ function formatArcLine(a) {
|
|||||||
|
|
||||||
// 完整 chunk 输出(不截断)
|
// 完整 chunk 输出(不截断)
|
||||||
function formatChunkFullLine(c) {
|
function formatChunkFullLine(c) {
|
||||||
const speaker = c.isUser ? "{{user}}" : "{{char}}";
|
const { name1, name2 } = getContext();
|
||||||
|
const speaker = c.isUser ? (name1 || "用户") : (name2 || "角色");
|
||||||
return `› #${c.floor + 1} [${speaker}] ${String(c.text || "").trim()}`;
|
return `› #${c.floor + 1} [${speaker}] ${String(c.text || "").trim()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,23 +292,19 @@ function buildNonVectorPrompt(store) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function injectNonVectorPrompt(postToFrame = null) {
|
export function buildNonVectorPromptText() {
|
||||||
if (!getSettings().storySummary?.enabled) {
|
if (!getSettings().storySummary?.enabled) {
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return "";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chat } = getContext();
|
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
if (!store?.json) {
|
if (!store?.json) {
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return "";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = buildNonVectorPrompt(store);
|
let text = buildNonVectorPrompt(store);
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return "";
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapper(沿用面板设置)
|
// wrapper(沿用面板设置)
|
||||||
@@ -323,21 +312,7 @@ export async function injectNonVectorPrompt(postToFrame = null) {
|
|||||||
if (cfg.trigger?.wrapperHead) text = cfg.trigger.wrapperHead + "\n" + text;
|
if (cfg.trigger?.wrapperHead) text = cfg.trigger.wrapperHead + "\n" + text;
|
||||||
if (cfg.trigger?.wrapperTail) text = text + "\n" + cfg.trigger.wrapperTail;
|
if (cfg.trigger?.wrapperTail) text = text + "\n" + cfg.trigger.wrapperTail;
|
||||||
|
|
||||||
const lastIdx = store.lastSummarizedMesId ?? 0;
|
return text;
|
||||||
let depth = (chat?.length || 0) - lastIdx - 1;
|
|
||||||
if (depth < 0) depth = 0;
|
|
||||||
if (cfg.trigger?.forceInsertAtEnd) depth = 10000;
|
|
||||||
|
|
||||||
extension_prompts[SUMMARY_PROMPT_KEY] = {
|
|
||||||
value: text,
|
|
||||||
position: extension_prompt_types.IN_CHAT,
|
|
||||||
depth,
|
|
||||||
role: extension_prompt_roles.SYSTEM,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (postToFrame) {
|
|
||||||
postToFrame({ type: "RECALL_LOG", text: "\n[Non-vector] Injected full summary prompt.\n" });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
@@ -706,19 +681,17 @@ async function attachEvidenceToCausalEvents(causalEvents, eventVectorMap, chunkV
|
|||||||
// ✅ 向量模式:召回 + 注入(供 story-summary.js 在 GENERATION_STARTED 调用)
|
// ✅ 向量模式:召回 + 注入(供 story-summary.js 在 GENERATION_STARTED 调用)
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
|
export async function buildVectorPromptText(excludeLastAi = false, hooks = {}) {
|
||||||
const { postToFrame = null, echo = null } = hooks;
|
const { postToFrame = null, echo = null, pendingUserMessage = null } = hooks;
|
||||||
if (!getSettings().storySummary?.enabled) {
|
if (!getSettings().storySummary?.enabled) {
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return { text: "", logText: "" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chat } = getContext();
|
const { chat } = getContext();
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
|
|
||||||
if (!store?.json) {
|
if (!store?.json) {
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return { text: "", logText: "" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allEvents = store.json.events || [];
|
const allEvents = store.json.events || [];
|
||||||
@@ -726,14 +699,12 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
|
|||||||
const length = chat?.length || 0;
|
const length = chat?.length || 0;
|
||||||
|
|
||||||
if (lastIdx >= length) {
|
if (lastIdx >= length) {
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return { text: "", logText: "" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const vectorCfg = getVectorConfig();
|
const vectorCfg = getVectorConfig();
|
||||||
if (!vectorCfg?.enabled) {
|
if (!vectorCfg?.enabled) {
|
||||||
// 向量没开,不该走这条
|
return { text: "", logText: "" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chatId } = getContext();
|
const { chatId } = getContext();
|
||||||
@@ -745,7 +716,10 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const queryText = buildQueryText(chat, 2, excludeLastAi);
|
const queryText = buildQueryText(chat, 2, excludeLastAi);
|
||||||
recallResult = await recallMemory(queryText, allEvents, vectorCfg, { excludeLastAi });
|
recallResult = await recallMemory(queryText, allEvents, vectorCfg, {
|
||||||
|
excludeLastAi,
|
||||||
|
pendingUserMessage,
|
||||||
|
});
|
||||||
|
|
||||||
recallResult = {
|
recallResult = {
|
||||||
...recallResult,
|
...recallResult,
|
||||||
@@ -808,9 +782,7 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空本次注入,避免残留误导
|
return { text: "", logText: `\n[Vector Recall Failed]\n${String(e?.stack || e?.message || e)}\n` };
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 成功但结果为空:也提示,并清空注入(不降级)
|
// 成功但结果为空:也提示,并清空注入(不降级)
|
||||||
@@ -831,8 +803,7 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
|
|||||||
text: "\n[Vector Recall Empty]\nNo recall candidates / vectors not ready.\n",
|
text: "\n[Vector Recall Empty]\nNo recall candidates / vectors not ready.\n",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
return { text: "", logText: "\n[Vector Recall Empty]\nNo recall candidates / vectors not ready.\n" };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼装向量 prompt
|
// 拼装向量 prompt
|
||||||
@@ -844,50 +815,17 @@ export async function recallAndInjectPrompt(excludeLastAi = false, hooks = {}) {
|
|||||||
meta
|
meta
|
||||||
);
|
);
|
||||||
|
|
||||||
// 写入 extension_prompts(真正注入)
|
// wrapper(沿用面板设置)——必须补回,否则语义回退
|
||||||
await writePromptToExtensionPrompts(promptText, store, chat);
|
const cfg = getSummaryPanelConfig();
|
||||||
|
let finalText = String(promptText || "");
|
||||||
|
if (cfg.trigger?.wrapperHead) finalText = cfg.trigger.wrapperHead + "\n" + finalText;
|
||||||
|
if (cfg.trigger?.wrapperTail) finalText = finalText + "\n" + cfg.trigger.wrapperTail;
|
||||||
|
|
||||||
// 发给涌现窗口:召回报告 + 装配报告
|
// 发给涌现窗口:召回报告 + 装配报告
|
||||||
if (postToFrame) {
|
if (postToFrame) {
|
||||||
const recallLog = recallResult.logText || "";
|
const recallLog = recallResult.logText || "";
|
||||||
postToFrame({ type: "RECALL_LOG", text: recallLog + (injectionLogText || "") });
|
postToFrame({ type: "RECALL_LOG", text: recallLog + (injectionLogText || "") });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return { text: finalText, logText: (recallResult.logText || "") + (injectionLogText || "") };
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// 写入 extension_prompts(统一入口)
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async function writePromptToExtensionPrompts(text, store, chat) {
|
|
||||||
const cfg = getSummaryPanelConfig();
|
|
||||||
let finalText = String(text || "");
|
|
||||||
|
|
||||||
if (cfg.trigger?.wrapperHead) finalText = cfg.trigger.wrapperHead + "\n" + finalText;
|
|
||||||
if (cfg.trigger?.wrapperTail) finalText = finalText + "\n" + cfg.trigger.wrapperTail;
|
|
||||||
|
|
||||||
if (!finalText.trim()) {
|
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastIdx = store.lastSummarizedMesId ?? 0;
|
|
||||||
let depth = (chat?.length || 0) - lastIdx - 1;
|
|
||||||
if (depth < 0) depth = 0;
|
|
||||||
|
|
||||||
if (cfg.trigger?.forceInsertAtEnd) depth = 10000;
|
|
||||||
|
|
||||||
extension_prompts[SUMMARY_PROMPT_KEY] = {
|
|
||||||
value: finalText,
|
|
||||||
position: extension_prompt_types.IN_CHAT,
|
|
||||||
depth,
|
|
||||||
role: extension_prompt_roles.SYSTEM,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// 清理 prompt(供 story-summary.js 调用)
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function clearSummaryExtensionPrompt() {
|
|
||||||
delete extension_prompts[SUMMARY_PROMPT_KEY];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// Story Summary - 主入口(干净版)
|
// Story Summary - 主入口(最终版)
|
||||||
// - 注入只在 GENERATION_STARTED 发生
|
//
|
||||||
// - 向量关闭:注入全量总结(L3+L2+Arcs)
|
// 稳定目标:
|
||||||
// - 向量开启:召回 + 1万预算装配注入
|
// 1) "聊天时隐藏已总结" 永远只隐藏"已总结"部分,绝不影响未总结部分
|
||||||
// - 删除所有 updateSummaryExtensionPrompt() 调用,避免覆盖/残留/竞态
|
// 2) 关闭隐藏 = 暴力全量 unhide,确保立刻恢复
|
||||||
|
// 3) 开启隐藏 / 改Y / 切Chat / 收新消息:先全量 unhide,再按边界重新 hide
|
||||||
|
// 4) Prompt 注入位置稳定:永远插在"最后一条 user 消息"之前
|
||||||
|
// 5) 注入不依赖 extension_prompts/depth,不受 ST 裁剪/隐藏参照系影响
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
import { getContext } from "../../../../../extensions.js";
|
import { getContext } from "../../../../../extensions.js";
|
||||||
@@ -23,11 +26,10 @@ import {
|
|||||||
clearSummaryData,
|
clearSummaryData,
|
||||||
} from "./data/store.js";
|
} from "./data/store.js";
|
||||||
|
|
||||||
// prompt injection (ONLY on generation started)
|
// prompt text builder
|
||||||
import {
|
import {
|
||||||
recallAndInjectPrompt,
|
buildVectorPromptText,
|
||||||
clearSummaryExtensionPrompt,
|
buildNonVectorPromptText,
|
||||||
injectNonVectorPrompt,
|
|
||||||
} from "./generate/prompt.js";
|
} from "./generate/prompt.js";
|
||||||
|
|
||||||
// summary generation
|
// summary generation
|
||||||
@@ -91,6 +93,13 @@ let pendingFrameMessages = [];
|
|||||||
let eventsRegistered = false;
|
let eventsRegistered = false;
|
||||||
let vectorGenerating = false;
|
let vectorGenerating = false;
|
||||||
let vectorCancelled = false;
|
let vectorCancelled = false;
|
||||||
|
let vectorAbortController = null;
|
||||||
|
|
||||||
|
let pendingInjectText = "";
|
||||||
|
|
||||||
|
// ★ 用户消息缓存(解决 GENERATION_STARTED 时 chat 尚未包含用户消息的问题)
|
||||||
|
let lastSentUserMessage = null;
|
||||||
|
let lastSentTimestamp = 0;
|
||||||
|
|
||||||
let hideApplyTimer = null;
|
let hideApplyTimer = null;
|
||||||
const HIDE_APPLY_DEBOUNCE_MS = 250;
|
const HIDE_APPLY_DEBOUNCE_MS = 250;
|
||||||
@@ -118,6 +127,18 @@ async function executeSlashCommand(command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLastMessageId() {
|
||||||
|
const { chat } = getContext();
|
||||||
|
const len = Array.isArray(chat) ? chat.length : 0;
|
||||||
|
return Math.max(-1, len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unhideAllMessages() {
|
||||||
|
const last = getLastMessageId();
|
||||||
|
if (last < 0) return;
|
||||||
|
await executeSlashCommand(`/unhide 0-${last}`);
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 生成状态管理
|
// 生成状态管理
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -302,6 +323,8 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
|
|
||||||
vectorGenerating = true;
|
vectorGenerating = true;
|
||||||
vectorCancelled = false;
|
vectorCancelled = false;
|
||||||
|
vectorAbortController?.abort?.();
|
||||||
|
vectorAbortController = new AbortController();
|
||||||
|
|
||||||
const fingerprint = getEngineFingerprint(vectorCfg);
|
const fingerprint = getEngineFingerprint(vectorCfg);
|
||||||
const isLocal = vectorCfg.engine === "local";
|
const isLocal = vectorCfg.engine === "local";
|
||||||
@@ -362,6 +385,8 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
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 });
|
||||||
|
|
||||||
|
let rateLimitWarned = false;
|
||||||
|
|
||||||
const allTasks = [...l1Batches, ...l2Batches];
|
const allTasks = [...l1Batches, ...l2Batches];
|
||||||
const l1Vectors = new Array(l1Texts.length);
|
const l1Vectors = new Array(l1Texts.length);
|
||||||
const l2VectorItems = [];
|
const l2VectorItems = [];
|
||||||
@@ -371,6 +396,7 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
async function worker() {
|
async function worker() {
|
||||||
while (taskIndex < allTasks.length) {
|
while (taskIndex < allTasks.length) {
|
||||||
if (vectorCancelled) break;
|
if (vectorCancelled) break;
|
||||||
|
if (vectorAbortController?.signal?.aborted) break;
|
||||||
|
|
||||||
const i = taskIndex++;
|
const i = taskIndex++;
|
||||||
if (i >= allTasks.length) break;
|
if (i >= allTasks.length) break;
|
||||||
@@ -378,7 +404,7 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
const task = allTasks[i];
|
const task = allTasks[i];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const vectors = await embed(task.texts, vectorCfg);
|
const vectors = await embed(task.texts, vectorCfg, { signal: vectorAbortController.signal });
|
||||||
|
|
||||||
if (task.phase === "L1") {
|
if (task.phase === "L1") {
|
||||||
for (let j = 0; j < vectors.length; j++) {
|
for (let j = 0; j < vectors.length; j++) {
|
||||||
@@ -404,7 +430,23 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (e?.name === "AbortError") {
|
||||||
|
xbLog.warn(MODULE_ID, "向量生成已取消(AbortError)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
xbLog.error(MODULE_ID, `${task.phase} batch 向量化失败`, e);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,6 +457,13 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
.map(() => worker())
|
.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) {
|
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))
|
||||||
@@ -433,6 +482,7 @@ async function handleGenerateVectors(vectorCfg) {
|
|||||||
|
|
||||||
vectorGenerating = false;
|
vectorGenerating = false;
|
||||||
vectorCancelled = false;
|
vectorCancelled = false;
|
||||||
|
vectorAbortController = null;
|
||||||
|
|
||||||
xbLog.info(MODULE_ID, `向量生成完成: L1=${l1Vectors.filter(Boolean).length}, L2=${l2VectorItems.length}`);
|
xbLog.info(MODULE_ID, `向量生成完成: L1=${l1Vectors.filter(Boolean).length}, L2=${l2VectorItems.length}`);
|
||||||
}
|
}
|
||||||
@@ -463,8 +513,6 @@ async function maybeAutoBuildChunks() {
|
|||||||
if (!isLocalModelLoaded(modelId)) return;
|
if (!isLocalModelLoaded(modelId)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
xbLog.info(MODULE_ID, `auto L1 chunks: pending=${status.pending}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await buildIncrementalChunks({ vectorConfig: cfg });
|
await buildIncrementalChunks({ vectorConfig: cfg });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -579,7 +627,6 @@ 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, "已从服务器加载面板配置");
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
xbLog.warn(MODULE_ID, "加载面板配置失败", e);
|
xbLog.warn(MODULE_ID, "加载面板配置失败", e);
|
||||||
@@ -633,7 +680,7 @@ function openPanelForMessage(mesId) {
|
|||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
const totalFloors = chat.length;
|
const totalFloors = chat.length;
|
||||||
|
|
||||||
sendFrameBaseData(store, totalFloors); // 不需要 await,fire-and-forget
|
sendFrameBaseData(store, totalFloors);
|
||||||
sendFrameFullData(store, totalFloors);
|
sendFrameFullData(store, totalFloors);
|
||||||
setSummaryGenerating(summaryGenerating);
|
setSummaryGenerating(summaryGenerating);
|
||||||
|
|
||||||
@@ -646,9 +693,9 @@ function openPanelForMessage(mesId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// Hide/Unhide:向量模式联动("已总结"的定义切换)
|
// Hide/Unhide
|
||||||
// - 非向量:boundary = lastSummarizedMesId(LLM总结边界)
|
// - 非向量:boundary = lastSummarizedMesId
|
||||||
// - 向量:boundary = meta.lastChunkFloor(已向量化)
|
// - 向量:boundary = meta.lastChunkFloor(若为 -1 则回退到 lastSummarizedMesId)
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function getHideBoundaryFloor(store) {
|
async function getHideBoundaryFloor(store) {
|
||||||
@@ -658,16 +705,21 @@ async function getHideBoundaryFloor(store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { chatId } = getContext();
|
const { chatId } = getContext();
|
||||||
if (!chatId) return -1;
|
if (!chatId) return store?.lastSummarizedMesId ?? -1;
|
||||||
|
|
||||||
const meta = await getMeta(chatId);
|
const meta = await getMeta(chatId);
|
||||||
return meta?.lastChunkFloor ?? -1;
|
const v = meta?.lastChunkFloor ?? -1;
|
||||||
|
if (v >= 0) return v;
|
||||||
|
return store?.lastSummarizedMesId ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyHideState() {
|
async function applyHideState() {
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
if (!store?.hideSummarizedHistory) return;
|
if (!store?.hideSummarizedHistory) return;
|
||||||
|
|
||||||
|
// 先全量 unhide,杜绝历史残留
|
||||||
|
await unhideAllMessages();
|
||||||
|
|
||||||
const boundary = await getHideBoundaryFloor(store);
|
const boundary = await getHideBoundaryFloor(store);
|
||||||
if (boundary < 0) return;
|
if (boundary < 0) return;
|
||||||
|
|
||||||
@@ -685,15 +737,12 @@ function applyHideStateDebounced() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clearHideState() {
|
async function clearHideState() {
|
||||||
const store = getSummaryStore();
|
// 暴力全量 unhide,确保立刻恢复
|
||||||
const boundary = await getHideBoundaryFloor(store);
|
await unhideAllMessages();
|
||||||
if (boundary < 0) return;
|
|
||||||
|
|
||||||
await executeSlashCommand(`/unhide 0-${boundary}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 自动总结(保持原逻辑;不做 prompt 注入)
|
// 自动总结
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function maybeAutoRunSummary(reason) {
|
async function maybeAutoRunSummary(reason) {
|
||||||
@@ -716,7 +765,6 @@ async function maybeAutoRunSummary(reason) {
|
|||||||
const pending = chat.length - lastSummarized - 1;
|
const pending = chat.length - lastSummarized - 1;
|
||||||
if (pending < (trig.interval || 1)) return;
|
if (pending < (trig.interval || 1)) return;
|
||||||
|
|
||||||
xbLog.info(MODULE_ID, `自动触发剧情总结: reason=${reason}, pending=${pending}`);
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,10 +787,8 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
|||||||
lastSummarizedMesId: endMesId,
|
lastSummarizedMesId: endMesId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
postToFrame({
|
|
||||||
type: "SUMMARY_STATUS",
|
applyHideStateDebounced();
|
||||||
statusText: `已更新至 ${endMesId + 1} 楼 · ${merged.events?.length || 0} 个事件`,
|
|
||||||
});
|
|
||||||
updateFrameStatsAfterSummary(endMesId, merged);
|
updateFrameStatsAfterSummary(endMesId, merged);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -756,7 +802,6 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSummaryGenerating(false);
|
setSummaryGenerating(false);
|
||||||
xbLog.error(MODULE_ID, "自动总结失败(已重试3次)");
|
|
||||||
await executeSlashCommand("/echo severity=error 剧情总结失败(已自动重试 3 次)。请稍后再试。");
|
await executeSlashCommand("/echo severity=error 剧情总结失败(已自动重试 3 次)。请稍后再试。");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,7 +874,6 @@ function handleFrameMessage(event) {
|
|||||||
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;
|
||||||
@@ -856,24 +900,21 @@ 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;
|
||||||
|
|
||||||
case "VECTOR_CANCEL_GENERATE":
|
case "VECTOR_CANCEL_GENERATE":
|
||||||
vectorCancelled = true;
|
vectorCancelled = true;
|
||||||
|
try { vectorAbortController?.abort?.(); } catch {}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// summary actions
|
|
||||||
case "REQUEST_CLEAR": {
|
case "REQUEST_CLEAR": {
|
||||||
const { chat, chatId } = getContext();
|
const { chat, chatId } = getContext();
|
||||||
clearSummaryData(chatId);
|
clearSummaryData(chatId);
|
||||||
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 },
|
||||||
@@ -920,16 +961,13 @@ function handleFrameMessage(event) {
|
|||||||
|
|
||||||
const oldCount = store.keepVisibleCount ?? 3;
|
const oldCount = store.keepVisibleCount ?? 3;
|
||||||
const newCount = Math.max(0, Math.min(50, parseInt(data.count) || 3));
|
const newCount = Math.max(0, Math.min(50, parseInt(data.count) || 3));
|
||||||
|
|
||||||
if (newCount === oldCount) break;
|
if (newCount === oldCount) break;
|
||||||
|
|
||||||
store.keepVisibleCount = newCount;
|
store.keepVisibleCount = newCount;
|
||||||
saveSummaryStore();
|
saveSummaryStore();
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// 先清掉原隐藏,再按新 keepCount 重算隐藏
|
|
||||||
if (store.hideSummarizedHistory) {
|
if (store.hideSummarizedHistory) {
|
||||||
await clearHideState();
|
|
||||||
await applyHideState();
|
await applyHideState();
|
||||||
}
|
}
|
||||||
const { chat } = getContext();
|
const { chat } = getContext();
|
||||||
@@ -941,8 +979,6 @@ 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);
|
||||||
clearSummaryExtensionPrompt(); // 配置变化立即清除注入,避免残留
|
|
||||||
xbLog.info(MODULE_ID, "面板配置已保存到服务器");
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -963,7 +999,6 @@ async function handleManualGenerate(mesId, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSummaryGenerating(true);
|
setSummaryGenerating(true);
|
||||||
xbLog.info(MODULE_ID, `开始手动总结 mesId=${mesId}`);
|
|
||||||
|
|
||||||
await runSummaryGeneration(mesId, config, {
|
await runSummaryGeneration(mesId, config, {
|
||||||
onStatus: (text) => postToFrame({ type: "SUMMARY_STATUS", statusText: text }),
|
onStatus: (text) => postToFrame({ type: "SUMMARY_STATUS", statusText: text }),
|
||||||
@@ -981,14 +1016,7 @@ async function handleManualGenerate(mesId, config) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
postToFrame({
|
|
||||||
type: "SUMMARY_STATUS",
|
|
||||||
statusText: `已更新至 ${endMesId + 1} 楼 · ${merged.events?.length || 0} 个事件`,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 隐藏逻辑:统一走 boundary(vector on/off 自动切换定义)
|
|
||||||
applyHideStateDebounced();
|
applyHideStateDebounced();
|
||||||
|
|
||||||
updateFrameStatsAfterSummary(endMesId, merged);
|
updateFrameStatsAfterSummary(endMesId, merged);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -997,7 +1025,7 @@ async function handleManualGenerate(mesId, config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// 事件处理器(不做 prompt 注入)
|
// 消息事件
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function handleChatChanged() {
|
async function handleChatChanged() {
|
||||||
@@ -1008,7 +1036,10 @@ async function handleChatChanged() {
|
|||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
|
|
||||||
const store = getSummaryStore();
|
const store = getSummaryStore();
|
||||||
applyHideStateDebounced();
|
|
||||||
|
if (store?.hideSummarizedHistory) {
|
||||||
|
await applyHideState();
|
||||||
|
}
|
||||||
|
|
||||||
if (frameReady) {
|
if (frameReady) {
|
||||||
await sendFrameBaseData(store, newLength);
|
await sendFrameBaseData(store, newLength);
|
||||||
@@ -1022,6 +1053,7 @@ async function handleMessageDeleted() {
|
|||||||
|
|
||||||
await rollbackSummaryIfNeeded();
|
await rollbackSummaryIfNeeded();
|
||||||
await syncOnMessageDeleted(chatId, newLength);
|
await syncOnMessageDeleted(chatId, newLength);
|
||||||
|
applyHideStateDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMessageSwiped() {
|
async function handleMessageSwiped() {
|
||||||
@@ -1030,6 +1062,7 @@ async function handleMessageSwiped() {
|
|||||||
|
|
||||||
await syncOnMessageSwiped(chatId, lastFloor);
|
await syncOnMessageSwiped(chatId, lastFloor);
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
|
applyHideStateDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMessageReceived() {
|
async function handleMessageReceived() {
|
||||||
@@ -1043,9 +1076,7 @@ async function handleMessageReceived() {
|
|||||||
await syncOnMessageReceived(chatId, lastFloor, message, vectorConfig);
|
await syncOnMessageReceived(chatId, lastFloor, message, vectorConfig);
|
||||||
await maybeAutoBuildChunks();
|
await maybeAutoBuildChunks();
|
||||||
|
|
||||||
// 向量模式下,lastChunkFloor 会持续推进;如果勾选隐藏,自动扩展隐藏范围
|
|
||||||
applyHideStateDebounced();
|
applyHideStateDebounced();
|
||||||
|
|
||||||
setTimeout(() => maybeAutoRunSummary("after_ai"), 1000);
|
setTimeout(() => maybeAutoRunSummary("after_ai"), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,6 +1088,7 @@ function handleMessageSent() {
|
|||||||
async function handleMessageUpdated() {
|
async function handleMessageUpdated() {
|
||||||
await rollbackSummaryIfNeeded();
|
await rollbackSummaryIfNeeded();
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
|
applyHideStateDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessageRendered(data) {
|
function handleMessageRendered(data) {
|
||||||
@@ -1066,7 +1098,20 @@ function handleMessageRendered(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// ✅ 唯一注入入口:GENERATION_STARTED
|
// 用户消息缓存(供向量召回使用)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
function handleMessageSentForRecall() {
|
||||||
|
const { chat } = getContext();
|
||||||
|
const lastMsg = chat?.[chat.length - 1];
|
||||||
|
if (lastMsg?.is_user) {
|
||||||
|
lastSentUserMessage = lastMsg.mes;
|
||||||
|
lastSentTimestamp = Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// Prompt 注入
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
async function handleGenerationStarted(type, _params, isDryRun) {
|
async function handleGenerationStarted(type, _params, isDryRun) {
|
||||||
@@ -1076,17 +1121,62 @@ async function handleGenerationStarted(type, _params, isDryRun) {
|
|||||||
const excludeLastAi = type === "swipe" || type === "regenerate";
|
const excludeLastAi = type === "swipe" || type === "regenerate";
|
||||||
const vectorCfg = getVectorConfig();
|
const vectorCfg = getVectorConfig();
|
||||||
|
|
||||||
// 向量模式:召回 + 预算装配
|
pendingInjectText = "";
|
||||||
|
|
||||||
|
// ★ 判断是否使用缓存的用户消息(30秒内有效)
|
||||||
|
let pendingUserMessage = null;
|
||||||
|
if (type === "normal" && lastSentUserMessage && (Date.now() - lastSentTimestamp < 30000)) {
|
||||||
|
pendingUserMessage = lastSentUserMessage;
|
||||||
|
}
|
||||||
|
// 用完清空
|
||||||
|
lastSentUserMessage = null;
|
||||||
|
lastSentTimestamp = 0;
|
||||||
|
|
||||||
if (vectorCfg?.enabled) {
|
if (vectorCfg?.enabled) {
|
||||||
await recallAndInjectPrompt(excludeLastAi, {
|
const r = await buildVectorPromptText(excludeLastAi, {
|
||||||
postToFrame,
|
postToFrame,
|
||||||
echo: executeSlashCommand, // recall failure notification
|
echo: executeSlashCommand,
|
||||||
|
pendingUserMessage,
|
||||||
});
|
});
|
||||||
|
pendingInjectText = r?.text || "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非向量模式:全量总结注入(不召回)
|
pendingInjectText = buildNonVectorPromptText() || "";
|
||||||
await injectNonVectorPrompt(postToFrame);
|
}
|
||||||
|
|
||||||
|
function clearPendingInject() {
|
||||||
|
pendingInjectText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function findInsertIndexBeforeLastUserMessage(chat) {
|
||||||
|
if (!Array.isArray(chat) || !chat.length) return 0;
|
||||||
|
for (let i = chat.length - 1; i >= 0; i--) {
|
||||||
|
if (chat[i]?.role === "user") return i;
|
||||||
|
}
|
||||||
|
// 没有 user,退化为末尾前一位
|
||||||
|
return Math.max(0, chat.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChatCompletionPromptReady(eventData) {
|
||||||
|
try {
|
||||||
|
if (!pendingInjectText?.trim()) return;
|
||||||
|
if (!eventData || eventData.dryRun) return;
|
||||||
|
if (!Array.isArray(eventData.chat)) return;
|
||||||
|
|
||||||
|
// 永远插在最后一条 user 消息之前,保证三段结构稳定
|
||||||
|
const insertAt = findInsertIndexBeforeLastUserMessage(eventData.chat);
|
||||||
|
|
||||||
|
eventData.chat.splice(insertAt, 0, {
|
||||||
|
role: "assistant",
|
||||||
|
content: pendingInjectText,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearPendingInject();
|
||||||
|
} catch (e) {
|
||||||
|
xbLog.error(MODULE_ID, "Prompt inject failed", e);
|
||||||
|
clearPendingInject();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -1097,8 +1187,6 @@ function registerEvents() {
|
|||||||
if (eventsRegistered) return;
|
if (eventsRegistered) return;
|
||||||
eventsRegistered = true;
|
eventsRegistered = true;
|
||||||
|
|
||||||
xbLog.info(MODULE_ID, "模块初始化");
|
|
||||||
|
|
||||||
CacheRegistry.register(MODULE_ID, {
|
CacheRegistry.register(MODULE_ID, {
|
||||||
name: "待发送消息队列",
|
name: "待发送消息队列",
|
||||||
getSize: () => pendingFrameMessages.length,
|
getSize: () => pendingFrameMessages.length,
|
||||||
@@ -1121,26 +1209,28 @@ function registerEvents() {
|
|||||||
eventSource.on(event_types.MESSAGE_DELETED, () => setTimeout(handleMessageDeleted, 50));
|
eventSource.on(event_types.MESSAGE_DELETED, () => setTimeout(handleMessageDeleted, 50));
|
||||||
eventSource.on(event_types.MESSAGE_RECEIVED, () => setTimeout(handleMessageReceived, 150));
|
eventSource.on(event_types.MESSAGE_RECEIVED, () => setTimeout(handleMessageReceived, 150));
|
||||||
eventSource.on(event_types.MESSAGE_SENT, () => setTimeout(handleMessageSent, 150));
|
eventSource.on(event_types.MESSAGE_SENT, () => setTimeout(handleMessageSent, 150));
|
||||||
|
eventSource.on(event_types.MESSAGE_SENT, handleMessageSentForRecall);
|
||||||
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);
|
||||||
|
eventSource.on(event_types.GENERATION_STOPPED, clearPendingInject);
|
||||||
|
eventSource.on(event_types.GENERATION_ENDED, clearPendingInject);
|
||||||
|
eventSource.on(event_types.CHAT_COMPLETION_PROMPT_READY, handleChatCompletionPromptReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisterEvents() {
|
function unregisterEvents() {
|
||||||
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();
|
||||||
|
|
||||||
// 禁用时清理注入,避免残留
|
clearPendingInject();
|
||||||
clearSummaryExtensionPrompt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -1151,9 +1241,6 @@ $(document).on("xiaobaix:storySummary:toggle", (_e, enabled) => {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
registerEvents();
|
registerEvents();
|
||||||
initButtonsForAll();
|
initButtonsForAll();
|
||||||
|
|
||||||
// 开启时清一次,防止旧注入残留
|
|
||||||
clearSummaryExtensionPrompt();
|
|
||||||
} else {
|
} else {
|
||||||
unregisterEvents();
|
unregisterEvents();
|
||||||
}
|
}
|
||||||
@@ -1164,12 +1251,6 @@ $(document).on("xiaobaix:storySummary:toggle", (_e, enabled) => {
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
jQuery(() => {
|
jQuery(() => {
|
||||||
if (!getSettings().storySummary?.enabled) {
|
if (!getSettings().storySummary?.enabled) return;
|
||||||
clearSummaryExtensionPrompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
registerEvents();
|
registerEvents();
|
||||||
|
|
||||||
// 初始化也清一次,保证干净(注入只在生成开始发生)
|
|
||||||
clearSummaryExtensionPrompt();
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ function buildExpDecayWeights(n, beta) {
|
|||||||
// Query 构建
|
// Query 构建
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
function buildQuerySegments(chat, count, excludeLastAi) {
|
function buildQuerySegments(chat, count, excludeLastAi, pendingUserMessage = null) {
|
||||||
if (!chat?.length) return [];
|
if (!chat?.length) return [];
|
||||||
|
|
||||||
let messages = chat;
|
let messages = chat;
|
||||||
@@ -166,6 +166,18 @@ function buildQuerySegments(chat, count, excludeLastAi) {
|
|||||||
messages = messages.slice(0, -1);
|
messages = messages.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ★ 如果有待处理的用户消息且 chat 中最后一条不是它,追加虚拟消息
|
||||||
|
if (pendingUserMessage) {
|
||||||
|
const lastMsg = messages[messages.length - 1];
|
||||||
|
const lastMsgText = lastMsg?.mes?.trim() || "";
|
||||||
|
const pendingText = pendingUserMessage.trim();
|
||||||
|
|
||||||
|
// 避免重复(如果 chat 已包含该消息则不追加)
|
||||||
|
if (lastMsgText !== pendingText) {
|
||||||
|
messages = [...messages, { is_user: true, name: "用户", mes: pendingUserMessage }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return messages.slice(-count).map((m, idx, arr) => {
|
return messages.slice(-count).map((m, idx, arr) => {
|
||||||
const speaker = m.name || (m.is_user ? '用户' : '角色');
|
const speaker = m.name || (m.is_user ? '用户' : '角色');
|
||||||
const clean = stripNoise(m.mes);
|
const clean = stripNoise(m.mes);
|
||||||
@@ -673,12 +685,13 @@ export async function recallMemory(queryText, allEvents, vectorConfig, options =
|
|||||||
export async function recallMemory(queryText, allEvents, vectorConfig, options = {}) {
|
export async function recallMemory(queryText, allEvents, vectorConfig, options = {}) {
|
||||||
const T0 = performance.now();
|
const T0 = performance.now();
|
||||||
const { chat } = getContext();
|
const { chat } = getContext();
|
||||||
|
const store = getSummaryStore();
|
||||||
const { pendingUserMessage = null } = options;
|
const { pendingUserMessage = null } = options;
|
||||||
|
|
||||||
if (!allEvents?.length) {
|
if (!allEvents?.length) {
|
||||||
return { events: [], chunks: [], elapsed: 0, logText: 'No events.' };
|
return { events: [], chunks: [], elapsed: 0, logText: 'No events.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const segments = buildQuerySegments(chat, CONFIG.QUERY_MSG_COUNT, !!options.excludeLastAi, pendingUserMessage);
|
const segments = buildQuerySegments(chat, CONFIG.QUERY_MSG_COUNT, !!options.excludeLastAi, pendingUserMessage);
|
||||||
|
|
||||||
let queryVector, weights;
|
let queryVector, weights;
|
||||||
|
|||||||
Reference in New Issue
Block a user