feat(story-summary): default hide summarized to enabled

This commit is contained in:
RT15548
2026-02-24 13:53:18 +08:00
parent 4ee528621f
commit a86aa999c3
6 changed files with 133 additions and 36 deletions

View File

@@ -19,6 +19,12 @@ export function getSettings() {
} }
export function getSummaryPanelConfig() { export function getSummaryPanelConfig() {
const clampKeepVisibleCount = (value) => {
const n = Number.parseInt(value, 10);
if (!Number.isFinite(n)) return 6;
return Math.max(0, Math.min(50, n));
};
const defaults = { const defaults = {
api: { provider: "st", url: "", key: "", model: "", modelCache: [] }, api: { provider: "st", url: "", key: "", model: "", modelCache: [] },
gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null }, gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null },
@@ -33,6 +39,10 @@ export function getSummaryPanelConfig() {
wrapperTail: "", wrapperTail: "",
forceInsertAtEnd: false, forceInsertAtEnd: false,
}, },
ui: {
hideSummarized: true,
keepVisibleCount: 6,
},
textFilterRules: [...DEFAULT_FILTER_RULES], textFilterRules: [...DEFAULT_FILTER_RULES],
vector: null, vector: null,
}; };
@@ -52,12 +62,15 @@ export function getSummaryPanelConfig() {
api: { ...defaults.api, ...(parsed.api || {}) }, api: { ...defaults.api, ...(parsed.api || {}) },
gen: { ...defaults.gen, ...(parsed.gen || {}) }, gen: { ...defaults.gen, ...(parsed.gen || {}) },
trigger: { ...defaults.trigger, ...(parsed.trigger || {}) }, trigger: { ...defaults.trigger, ...(parsed.trigger || {}) },
ui: { ...defaults.ui, ...(parsed.ui || {}) },
textFilterRules, textFilterRules,
vector: parsed.vector || null, vector: parsed.vector || null,
}; };
if (result.trigger.timing === "manual") result.trigger.enabled = false; if (result.trigger.timing === "manual") result.trigger.enabled = false;
if (result.trigger.useStream === undefined) result.trigger.useStream = true; if (result.trigger.useStream === undefined) result.trigger.useStream = true;
result.ui.hideSummarized = !!result.ui.hideSummarized;
result.ui.keepVisibleCount = clampKeepVisibleCount(result.ui.keepVisibleCount);
return result; return result;
} catch { } catch {

View File

@@ -48,13 +48,15 @@ export function saveSummaryStore() {
export function getKeepVisibleCount() { export function getKeepVisibleCount() {
const store = getSummaryStore(); const store = getSummaryStore();
return store?.keepVisibleCount ?? 3; return store?.keepVisibleCount ?? 6;
} }
export function calcHideRange(boundary) { export function calcHideRange(boundary, keepCountOverride = null) {
if (boundary == null || boundary < 0) return null; if (boundary == null || boundary < 0) return null;
const keepCount = getKeepVisibleCount(); const keepCount = Number.isFinite(keepCountOverride)
? Math.max(0, Math.min(50, Number(keepCountOverride)))
: getKeepVisibleCount();
const hideEnd = boundary - keepCount; const hideEnd = boundary - keepCount;
if (hideEnd < 0) return null; if (hideEnd < 0) return null;
return { start: 0, end: hideEnd }; return { start: 0, end: hideEnd };

View File

@@ -210,11 +210,13 @@ Before generating, observe the USER and analyze carefully:
## CRITICAL NOTES ## CRITICAL NOTES
- events.id 从 evt-{nextEventId} 开始编号 - events.id 从 evt-{nextEventId} 开始编号
- 仅输出【增量】内容,已有事件绝不重复 - 仅输出【增量】内容,已有事件绝不重复
- /地点、通过什么方式、对谁、做了什么事、结果如何。如果原文有具体道具(如一把枪、一封信),必须在总结中提及。
- keywords 是全局关键词,综合已有+新增 - keywords 是全局关键词,综合已有+新增
- causedBy 仅在因果明确时填写,允许为[]0-2个 - causedBy 仅在因果明确时填写,允许为[]0-2个
- factUpdates 可为空数组 - factUpdates 可为空数组
- 合法JSON字符串值内部避免英文双引号 - 合法JSON字符串值内部避免英文双引号
- 用朴实、白描、有烟火气的笔触记录,避免比喻和意象 - 用朴实、白描、有烟火气的笔触记录事实,避免比喻和意象
- 严谨、注重细节,避免使用模糊的概括性语言,应用具体的动词描述动作,例:谁,在什么时间/地点,通过什么方式,对谁,做了什么事,出现了什么道具,结果如何。
</meta_protocol>`, </meta_protocol>`,
assistantCheck: `Content review initiated... assistantCheck: `Content review initiated...

View File

@@ -18,6 +18,7 @@ import { getSummaryStore, getFacts, isRelationFact } from "../data/store.js";
import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js"; import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js";
import { recallMemory } from "../vector/retrieval/recall.js"; import { recallMemory } from "../vector/retrieval/recall.js";
import { getMeta } from "../vector/storage/chunk-store.js"; import { getMeta } from "../vector/storage/chunk-store.js";
import { getStateAtoms } from "../vector/storage/state-store.js";
import { getEngineFingerprint } from "../vector/utils/embedder.js"; import { getEngineFingerprint } from "../vector/utils/embedder.js";
import { buildTrustedCharacters } from "../vector/retrieval/entity-lexicon.js"; import { buildTrustedCharacters } from "../vector/retrieval/entity-lexicon.js";
@@ -540,6 +541,34 @@ function groupL0ByFloor(l0List) {
return map; return map;
} }
/**
* Get all available L0 atoms in recent window and normalize to evidence shape.
* @param {number} recentStart
* @param {number} recentEnd
* @returns {object[]}
*/
function getRecentWindowL0Atoms(recentStart, recentEnd) {
if (!Number.isFinite(recentStart) || !Number.isFinite(recentEnd) || recentEnd < recentStart) return [];
const atoms = getStateAtoms() || [];
const out = [];
for (const atom of atoms) {
const floor = atom?.floor;
const atomId = atom?.atomId;
const semantic = String(atom?.semantic || '').trim();
if (!Number.isFinite(floor)) continue;
if (floor < recentStart || floor > recentEnd) continue;
if (!atomId || !semantic) continue;
out.push({
id: atomId,
floor,
atom,
similarity: 0,
rerankScore: 0,
});
}
return out;
}
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// EvidenceGroupper-floorN个L0 + 共享一对L1 // EvidenceGroupper-floorN个L0 + 共享一对L1
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
@@ -585,6 +614,21 @@ function buildEvidenceGroup(floor, l0AtomsForFloor, l1ByFloor) {
return { floor, l0Atoms: l0AtomsForFloor, userL1, aiL1, totalTokens }; return { floor, l0Atoms: l0AtomsForFloor, userL1, aiL1, totalTokens };
} }
/**
* Build recent-evidence group (L0 only, no L1 attachment).
* @param {number} floor
* @param {object[]} l0AtomsForFloor
* @returns {object}
*/
function buildRecentEvidenceGroup(floor, l0AtomsForFloor) {
let totalTokens = 0;
for (const l0 of l0AtomsForFloor) {
totalTokens += estimateTokens(buildL0DisplayText(l0));
}
totalTokens += 10;
return { floor, l0Atoms: l0AtomsForFloor, userL1: null, aiL1: null, totalTokens };
}
/** /**
* 格式化一个证据组为文本行数组 * 格式化一个证据组为文本行数组
* *
@@ -1114,7 +1158,11 @@ async function buildVectorPrompt(store, recallResult, causalById, focusCharacter
const lastSummarized = store.lastSummarizedMesId ?? -1; const lastSummarized = store.lastSummarizedMesId ?? -1;
const lastChunkFloor = meta?.lastChunkFloor ?? -1; const lastChunkFloor = meta?.lastChunkFloor ?? -1;
const keepVisible = store.keepVisibleCount ?? 3; const uiCfg = getSummaryPanelConfig()?.ui || {};
const parsedKeepVisible = Number.parseInt(uiCfg.keepVisibleCount, 10);
const keepVisible = Number.isFinite(parsedKeepVisible)
? Math.max(0, Math.min(50, parsedKeepVisible))
: 6;
// 收集未被事件消费的 L0按 rerankScore 降序 // 收集未被事件消费的 L0按 rerankScore 降序
const focusSetForEvidence = new Set((focusCharacters || []).map(normalize).filter(Boolean)); const focusSetForEvidence = new Set((focusCharacters || []).map(normalize).filter(Boolean));
@@ -1171,22 +1219,22 @@ async function buildVectorPrompt(store, recallResult, causalById, focusCharacter
const recentEnd = lastChunkFloor - keepVisible; const recentEnd = lastChunkFloor - keepVisible;
if (recentEnd >= recentStart) { if (recentEnd >= recentStart) {
const recentL0 = remainingL0 const recentAllL0 = getRecentWindowL0Atoms(recentStart, recentEnd);
const recentL0 = recentAllL0
.filter(l0 => !usedL0Ids.has(l0.id)) .filter(l0 => !usedL0Ids.has(l0.id))
.filter(l0 => l0.floor >= recentStart && l0.floor <= recentEnd); .filter(l0 => l0.floor >= recentStart && l0.floor <= recentEnd);
if (recentL0.length) { if (recentL0.length) {
const recentBudget = { used: 0, max: UNSUMMARIZED_EVIDENCE_MAX }; const recentBudget = { used: 0, max: UNSUMMARIZED_EVIDENCE_MAX };
// 先按分数挑组(高分优先),再按时间输出(楼层升序) // Pick newest floors first, then output in chronological order.
const recentFloorMap = groupL0ByFloor(recentL0); const recentFloorMap = groupL0ByFloor(recentL0);
const recentRanked = []; const recentRanked = [];
for (const [floor, l0s] of recentFloorMap) { for (const [floor, l0s] of recentFloorMap) {
const group = buildEvidenceGroup(floor, l0s, l1ByFloor); const group = buildRecentEvidenceGroup(floor, l0s);
const bestScore = Math.max(...l0s.map(l0 => (l0.rerankScore ?? l0.similarity ?? 0))); recentRanked.push({ group });
recentRanked.push({ group, bestScore });
} }
recentRanked.sort((a, b) => (b.bestScore - a.bestScore) || (a.group.floor - b.group.floor)); recentRanked.sort((a, b) => b.group.floor - a.group.floor);
const acceptedRecentGroups = []; const acceptedRecentGroups = [];
for (const item of recentRanked) { for (const item of recentRanked) {
@@ -1277,6 +1325,8 @@ async function buildVectorPrompt(store, recallResult, causalById, focusCharacter
}; };
metrics.evidence.tokens = injectionStats.distantEvidence.tokens + injectionStats.recentEvidence.tokens; metrics.evidence.tokens = injectionStats.distantEvidence.tokens + injectionStats.recentEvidence.tokens;
metrics.evidence.recentSource = 'all_l0_window';
metrics.evidence.recentL1Attached = 0;
metrics.evidence.assemblyTime = Math.round( metrics.evidence.assemblyTime = Math.round(
performance.now() - T_Start - (metrics.timing.constraintFilter || 0) - metrics.formatting.time performance.now() - T_Start - (metrics.timing.constraintFilter || 0) - metrics.formatting.time
); );

View File

@@ -87,6 +87,7 @@
api: { provider: 'st', url: '', key: '', model: '', modelCache: [] }, api: { provider: 'st', url: '', key: '', model: '', modelCache: [] },
gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null }, gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null },
trigger: { enabled: false, interval: 20, timing: 'before_user', role: 'system', useStream: true, maxPerRun: 100, wrapperHead: '', wrapperTail: '', forceInsertAtEnd: false }, trigger: { enabled: false, interval: 20, timing: 'before_user', role: 'system', useStream: true, maxPerRun: 100, wrapperHead: '', wrapperTail: '', forceInsertAtEnd: false },
ui: { hideSummarized: true, keepVisibleCount: 6 },
textFilterRules: [...DEFAULT_FILTER_RULES], textFilterRules: [...DEFAULT_FILTER_RULES],
vector: { enabled: false, engine: 'online', local: { modelId: 'bge-small-zh' }, online: { provider: 'siliconflow', url: '', key: '', model: '' } } vector: { enabled: false, engine: 'online', local: { modelId: 'bge-small-zh' }, online: { provider: 'siliconflow', url: '', key: '', model: '' } }
}; };
@@ -124,6 +125,7 @@
Object.assign(config.api, p.api || {}); Object.assign(config.api, p.api || {});
Object.assign(config.gen, p.gen || {}); Object.assign(config.gen, p.gen || {});
Object.assign(config.trigger, p.trigger || {}); Object.assign(config.trigger, p.trigger || {});
Object.assign(config.ui, p.ui || {});
config.textFilterRules = Array.isArray(p.textFilterRules) config.textFilterRules = Array.isArray(p.textFilterRules)
? p.textFilterRules ? p.textFilterRules
: (Array.isArray(p.vector?.textFilterRules) ? p.vector.textFilterRules : [...DEFAULT_FILTER_RULES]); : (Array.isArray(p.vector?.textFilterRules) ? p.vector.textFilterRules : [...DEFAULT_FILTER_RULES]);
@@ -141,6 +143,7 @@
Object.assign(config.api, cfg.api || {}); Object.assign(config.api, cfg.api || {});
Object.assign(config.gen, cfg.gen || {}); Object.assign(config.gen, cfg.gen || {});
Object.assign(config.trigger, cfg.trigger || {}); Object.assign(config.trigger, cfg.trigger || {});
Object.assign(config.ui, cfg.ui || {});
config.textFilterRules = Array.isArray(cfg.textFilterRules) config.textFilterRules = Array.isArray(cfg.textFilterRules)
? cfg.textFilterRules ? cfg.textFilterRules
: (Array.isArray(cfg.vector?.textFilterRules) : (Array.isArray(cfg.vector?.textFilterRules)
@@ -1599,7 +1602,8 @@
// Hide summarized // Hide summarized
$('hide-summarized').onchange = e => postMsg('TOGGLE_HIDE_SUMMARIZED', { enabled: e.target.checked }); $('hide-summarized').onchange = e => postMsg('TOGGLE_HIDE_SUMMARIZED', { enabled: e.target.checked });
$('keep-visible-count').onchange = e => { $('keep-visible-count').onchange = e => {
const c = Math.max(0, Math.min(50, parseInt(e.target.value) || 3)); const parsedCount = Number.parseInt(e.target.value, 10);
const c = Number.isFinite(parsedCount) ? Math.max(0, Math.min(50, parsedCount)) : 6;
e.target.value = c; e.target.value = c;
postMsg('UPDATE_KEEP_VISIBLE', { count: c }); postMsg('UPDATE_KEEP_VISIBLE', { count: c });
}; };

View File

@@ -22,7 +22,7 @@ import { postToIframe, isTrustedMessage } from "../../core/iframe-messaging.js";
import { CommonSettingStorage } from "../../core/server-storage.js"; import { CommonSettingStorage } from "../../core/server-storage.js";
// config/store // config/store
import { getSettings, getSummaryPanelConfig, getVectorConfig, saveVectorConfig } from "./data/config.js"; import { getSettings, getSummaryPanelConfig, getVectorConfig, saveVectorConfig, saveSummaryPanelConfig } from "./data/config.js";
import { import {
getSummaryStore, getSummaryStore,
saveSummaryStore, saveSummaryStore,
@@ -950,10 +950,41 @@ async function sendSavedConfigToFrame() {
} }
} }
function getHideUiSettings() {
const cfg = getSummaryPanelConfig() || {};
const ui = cfg.ui || {};
const parsedKeep = Number.parseInt(ui.keepVisibleCount, 10);
const keepVisibleCount = Number.isFinite(parsedKeep) ? Math.max(0, Math.min(50, parsedKeep)) : 6;
return {
hideSummarized: !!ui.hideSummarized,
keepVisibleCount,
};
}
function setHideUiSettings(patch = {}) {
const cfg = getSummaryPanelConfig() || {};
const current = getHideUiSettings();
const next = {
...cfg,
ui: {
hideSummarized: patch.hideSummarized !== undefined ? !!patch.hideSummarized : current.hideSummarized,
keepVisibleCount: patch.keepVisibleCount !== undefined
? (() => {
const parsedKeep = Number.parseInt(patch.keepVisibleCount, 10);
return Number.isFinite(parsedKeep) ? Math.max(0, Math.min(50, parsedKeep)) : 6;
})()
: current.keepVisibleCount,
},
};
saveSummaryPanelConfig(next);
return next.ui;
}
async function sendFrameBaseData(store, totalFloors) { async function sendFrameBaseData(store, totalFloors) {
const ui = getHideUiSettings();
const boundary = await getHideBoundaryFloor(store); const boundary = await getHideBoundaryFloor(store);
const range = calcHideRange(boundary); const range = calcHideRange(boundary, ui.keepVisibleCount);
const hiddenCount = (store?.hideSummarizedHistory && range) ? (range.end + 1) : 0; const hiddenCount = (ui.hideSummarized && range) ? (range.end + 1) : 0;
const lastSummarized = store?.lastSummarizedMesId ?? -1; const lastSummarized = store?.lastSummarizedMesId ?? -1;
postToFrame({ postToFrame({
@@ -965,8 +996,8 @@ async function sendFrameBaseData(store, totalFloors) {
pendingFloors: totalFloors - lastSummarized - 1, pendingFloors: totalFloors - lastSummarized - 1,
hiddenCount, hiddenCount,
}, },
hideSummarized: store?.hideSummarizedHistory || false, hideSummarized: ui.hideSummarized,
keepVisibleCount: store?.keepVisibleCount ?? 3, keepVisibleCount: ui.keepVisibleCount,
}); });
} }
@@ -1041,7 +1072,8 @@ async function getHideBoundaryFloor(store) {
async function applyHideState() { async function applyHideState() {
const store = getSummaryStore(); const store = getSummaryStore();
if (!store?.hideSummarizedHistory) return; const ui = getHideUiSettings();
if (!ui.hideSummarized) return;
// 先全量 unhide杜绝历史残留 // 先全量 unhide杜绝历史残留
await unhideAllMessages(); await unhideAllMessages();
@@ -1049,7 +1081,7 @@ async function applyHideState() {
const boundary = await getHideBoundaryFloor(store); const boundary = await getHideBoundaryFloor(store);
if (boundary < 0) return; if (boundary < 0) return;
const range = calcHideRange(boundary); const range = calcHideRange(boundary, ui.keepVisibleCount);
if (!range) return; if (!range) return;
await executeSlashCommand(`/hide ${range.start}-${range.end}`); await executeSlashCommand(`/hide ${range.start}-${range.end}`);
@@ -1149,9 +1181,9 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
function updateFrameStatsAfterSummary(endMesId, merged) { function updateFrameStatsAfterSummary(endMesId, merged) {
const { chat } = getContext(); const { chat } = getContext();
const totalFloors = Array.isArray(chat) ? chat.length : 0; const totalFloors = Array.isArray(chat) ? chat.length : 0;
const store = getSummaryStore(); const ui = getHideUiSettings();
const range = calcHideRange(endMesId); const range = calcHideRange(endMesId, ui.keepVisibleCount);
const hiddenCount = store?.hideSummarizedHistory && range ? range.end + 1 : 0; const hiddenCount = ui.hideSummarized && range ? range.end + 1 : 0;
postToFrame({ postToFrame({
type: "SUMMARY_BASE_DATA", type: "SUMMARY_BASE_DATA",
@@ -1347,11 +1379,7 @@ async function handleFrameMessage(event) {
} }
case "TOGGLE_HIDE_SUMMARIZED": { case "TOGGLE_HIDE_SUMMARIZED": {
const store = getSummaryStore(); setHideUiSettings({ hideSummarized: !!data.enabled });
if (!store) break;
store.hideSummarizedHistory = !!data.enabled;
saveSummaryStore();
(async () => { (async () => {
if (data.enabled) { if (data.enabled) {
@@ -1364,21 +1392,19 @@ async function handleFrameMessage(event) {
} }
case "UPDATE_KEEP_VISIBLE": { case "UPDATE_KEEP_VISIBLE": {
const store = getSummaryStore(); const oldCount = getHideUiSettings().keepVisibleCount;
if (!store) break; const parsedCount = Number.parseInt(data.count, 10);
const newCount = Number.isFinite(parsedCount) ? Math.max(0, Math.min(50, parsedCount)) : 6;
const oldCount = store.keepVisibleCount ?? 3;
const newCount = Math.max(0, Math.min(50, parseInt(data.count) || 3));
if (newCount === oldCount) break; if (newCount === oldCount) break;
store.keepVisibleCount = newCount; setHideUiSettings({ keepVisibleCount: newCount });
saveSummaryStore();
(async () => { (async () => {
if (store.hideSummarizedHistory) { if (getHideUiSettings().hideSummarized) {
await applyHideState(); await applyHideState();
} }
const { chat } = getContext(); const { chat } = getContext();
const store = getSummaryStore();
await sendFrameBaseData(store, Array.isArray(chat) ? chat.length : 0); await sendFrameBaseData(store, Array.isArray(chat) ? chat.length : 0);
})(); })();
break; break;
@@ -1452,7 +1478,7 @@ async function handleChatChanged() {
const store = getSummaryStore(); const store = getSummaryStore();
if (store?.hideSummarizedHistory) { if (getHideUiSettings().hideSummarized) {
await applyHideState(); await applyHideState();
} }