feat(story-summary): default hide summarized to enabled
This commit is contained in:
@@ -19,6 +19,12 @@ export function getSettings() {
|
||||
}
|
||||
|
||||
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 = {
|
||||
api: { provider: "st", url: "", key: "", model: "", modelCache: [] },
|
||||
gen: { temperature: null, top_p: null, top_k: null, presence_penalty: null, frequency_penalty: null },
|
||||
@@ -33,6 +39,10 @@ export function getSummaryPanelConfig() {
|
||||
wrapperTail: "",
|
||||
forceInsertAtEnd: false,
|
||||
},
|
||||
ui: {
|
||||
hideSummarized: true,
|
||||
keepVisibleCount: 6,
|
||||
},
|
||||
textFilterRules: [...DEFAULT_FILTER_RULES],
|
||||
vector: null,
|
||||
};
|
||||
@@ -52,12 +62,15 @@ export function getSummaryPanelConfig() {
|
||||
api: { ...defaults.api, ...(parsed.api || {}) },
|
||||
gen: { ...defaults.gen, ...(parsed.gen || {}) },
|
||||
trigger: { ...defaults.trigger, ...(parsed.trigger || {}) },
|
||||
ui: { ...defaults.ui, ...(parsed.ui || {}) },
|
||||
textFilterRules,
|
||||
vector: parsed.vector || null,
|
||||
};
|
||||
|
||||
if (result.trigger.timing === "manual") result.trigger.enabled = false;
|
||||
if (result.trigger.useStream === undefined) result.trigger.useStream = true;
|
||||
result.ui.hideSummarized = !!result.ui.hideSummarized;
|
||||
result.ui.keepVisibleCount = clampKeepVisibleCount(result.ui.keepVisibleCount);
|
||||
|
||||
return result;
|
||||
} catch {
|
||||
|
||||
@@ -48,13 +48,15 @@ export function saveSummaryStore() {
|
||||
|
||||
export function getKeepVisibleCount() {
|
||||
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;
|
||||
|
||||
const keepCount = getKeepVisibleCount();
|
||||
const keepCount = Number.isFinite(keepCountOverride)
|
||||
? Math.max(0, Math.min(50, Number(keepCountOverride)))
|
||||
: getKeepVisibleCount();
|
||||
const hideEnd = boundary - keepCount;
|
||||
if (hideEnd < 0) return null;
|
||||
return { start: 0, end: hideEnd };
|
||||
|
||||
@@ -210,11 +210,13 @@ Before generating, observe the USER and analyze carefully:
|
||||
## CRITICAL NOTES
|
||||
- events.id 从 evt-{nextEventId} 开始编号
|
||||
- 仅输出【增量】内容,已有事件绝不重复
|
||||
- /地点、通过什么方式、对谁、做了什么事、结果如何。如果原文有具体道具(如一把枪、一封信),必须在总结中提及。
|
||||
- keywords 是全局关键词,综合已有+新增
|
||||
- causedBy 仅在因果明确时填写,允许为[],0-2个
|
||||
- factUpdates 可为空数组
|
||||
- 合法JSON,字符串值内部避免英文双引号
|
||||
- 用朴实、白描、有烟火气的笔触记录,避免比喻和意象
|
||||
- 用朴实、白描、有烟火气的笔触记录事实,避免比喻和意象
|
||||
- 严谨、注重细节,避免使用模糊的概括性语言,应用具体的动词描述动作,例:谁,在什么时间/地点,通过什么方式,对谁,做了什么事,出现了什么道具,结果如何。
|
||||
</meta_protocol>`,
|
||||
|
||||
assistantCheck: `Content review initiated...
|
||||
|
||||
@@ -18,6 +18,7 @@ import { getSummaryStore, getFacts, isRelationFact } from "../data/store.js";
|
||||
import { getVectorConfig, getSummaryPanelConfig, getSettings } from "../data/config.js";
|
||||
import { recallMemory } from "../vector/retrieval/recall.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 { buildTrustedCharacters } from "../vector/retrieval/entity-lexicon.js";
|
||||
|
||||
@@ -540,6 +541,34 @@ function groupL0ByFloor(l0List) {
|
||||
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;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// EvidenceGroup(per-floor:N个L0 + 共享一对L1)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -585,6 +614,21 @@ function buildEvidenceGroup(floor, l0AtomsForFloor, l1ByFloor) {
|
||||
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 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 降序
|
||||
const focusSetForEvidence = new Set((focusCharacters || []).map(normalize).filter(Boolean));
|
||||
@@ -1171,22 +1219,22 @@ async function buildVectorPrompt(store, recallResult, causalById, focusCharacter
|
||||
const recentEnd = lastChunkFloor - keepVisible;
|
||||
|
||||
if (recentEnd >= recentStart) {
|
||||
const recentL0 = remainingL0
|
||||
const recentAllL0 = getRecentWindowL0Atoms(recentStart, recentEnd);
|
||||
const recentL0 = recentAllL0
|
||||
.filter(l0 => !usedL0Ids.has(l0.id))
|
||||
.filter(l0 => l0.floor >= recentStart && l0.floor <= recentEnd);
|
||||
|
||||
if (recentL0.length) {
|
||||
const recentBudget = { used: 0, max: UNSUMMARIZED_EVIDENCE_MAX };
|
||||
|
||||
// 先按分数挑组(高分优先),再按时间输出(楼层升序)
|
||||
// Pick newest floors first, then output in chronological order.
|
||||
const recentFloorMap = groupL0ByFloor(recentL0);
|
||||
const recentRanked = [];
|
||||
for (const [floor, l0s] of recentFloorMap) {
|
||||
const group = buildEvidenceGroup(floor, l0s, l1ByFloor);
|
||||
const bestScore = Math.max(...l0s.map(l0 => (l0.rerankScore ?? l0.similarity ?? 0)));
|
||||
recentRanked.push({ group, bestScore });
|
||||
const group = buildRecentEvidenceGroup(floor, l0s);
|
||||
recentRanked.push({ group });
|
||||
}
|
||||
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 = [];
|
||||
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.recentSource = 'all_l0_window';
|
||||
metrics.evidence.recentL1Attached = 0;
|
||||
metrics.evidence.assemblyTime = Math.round(
|
||||
performance.now() - T_Start - (metrics.timing.constraintFilter || 0) - metrics.formatting.time
|
||||
);
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
api: { provider: 'st', url: '', key: '', model: '', modelCache: [] },
|
||||
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 },
|
||||
ui: { hideSummarized: true, keepVisibleCount: 6 },
|
||||
textFilterRules: [...DEFAULT_FILTER_RULES],
|
||||
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.gen, p.gen || {});
|
||||
Object.assign(config.trigger, p.trigger || {});
|
||||
Object.assign(config.ui, p.ui || {});
|
||||
config.textFilterRules = Array.isArray(p.textFilterRules)
|
||||
? p.textFilterRules
|
||||
: (Array.isArray(p.vector?.textFilterRules) ? p.vector.textFilterRules : [...DEFAULT_FILTER_RULES]);
|
||||
@@ -141,6 +143,7 @@
|
||||
Object.assign(config.api, cfg.api || {});
|
||||
Object.assign(config.gen, cfg.gen || {});
|
||||
Object.assign(config.trigger, cfg.trigger || {});
|
||||
Object.assign(config.ui, cfg.ui || {});
|
||||
config.textFilterRules = Array.isArray(cfg.textFilterRules)
|
||||
? cfg.textFilterRules
|
||||
: (Array.isArray(cfg.vector?.textFilterRules)
|
||||
@@ -1599,7 +1602,8 @@
|
||||
// Hide summarized
|
||||
$('hide-summarized').onchange = e => postMsg('TOGGLE_HIDE_SUMMARIZED', { enabled: e.target.checked });
|
||||
$('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;
|
||||
postMsg('UPDATE_KEEP_VISIBLE', { count: c });
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ import { postToIframe, isTrustedMessage } from "../../core/iframe-messaging.js";
|
||||
import { CommonSettingStorage } from "../../core/server-storage.js";
|
||||
|
||||
// config/store
|
||||
import { getSettings, getSummaryPanelConfig, getVectorConfig, saveVectorConfig } from "./data/config.js";
|
||||
import { getSettings, getSummaryPanelConfig, getVectorConfig, saveVectorConfig, saveSummaryPanelConfig } from "./data/config.js";
|
||||
import {
|
||||
getSummaryStore,
|
||||
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) {
|
||||
const ui = getHideUiSettings();
|
||||
const boundary = await getHideBoundaryFloor(store);
|
||||
const range = calcHideRange(boundary);
|
||||
const hiddenCount = (store?.hideSummarizedHistory && range) ? (range.end + 1) : 0;
|
||||
const range = calcHideRange(boundary, ui.keepVisibleCount);
|
||||
const hiddenCount = (ui.hideSummarized && range) ? (range.end + 1) : 0;
|
||||
|
||||
const lastSummarized = store?.lastSummarizedMesId ?? -1;
|
||||
postToFrame({
|
||||
@@ -965,8 +996,8 @@ async function sendFrameBaseData(store, totalFloors) {
|
||||
pendingFloors: totalFloors - lastSummarized - 1,
|
||||
hiddenCount,
|
||||
},
|
||||
hideSummarized: store?.hideSummarizedHistory || false,
|
||||
keepVisibleCount: store?.keepVisibleCount ?? 3,
|
||||
hideSummarized: ui.hideSummarized,
|
||||
keepVisibleCount: ui.keepVisibleCount,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1041,7 +1072,8 @@ async function getHideBoundaryFloor(store) {
|
||||
|
||||
async function applyHideState() {
|
||||
const store = getSummaryStore();
|
||||
if (!store?.hideSummarizedHistory) return;
|
||||
const ui = getHideUiSettings();
|
||||
if (!ui.hideSummarized) return;
|
||||
|
||||
// 先全量 unhide,杜绝历史残留
|
||||
await unhideAllMessages();
|
||||
@@ -1049,7 +1081,7 @@ async function applyHideState() {
|
||||
const boundary = await getHideBoundaryFloor(store);
|
||||
if (boundary < 0) return;
|
||||
|
||||
const range = calcHideRange(boundary);
|
||||
const range = calcHideRange(boundary, ui.keepVisibleCount);
|
||||
if (!range) return;
|
||||
|
||||
await executeSlashCommand(`/hide ${range.start}-${range.end}`);
|
||||
@@ -1149,9 +1181,9 @@ async function autoRunSummaryWithRetry(targetMesId, configForRun) {
|
||||
function updateFrameStatsAfterSummary(endMesId, merged) {
|
||||
const { chat } = getContext();
|
||||
const totalFloors = Array.isArray(chat) ? chat.length : 0;
|
||||
const store = getSummaryStore();
|
||||
const range = calcHideRange(endMesId);
|
||||
const hiddenCount = store?.hideSummarizedHistory && range ? range.end + 1 : 0;
|
||||
const ui = getHideUiSettings();
|
||||
const range = calcHideRange(endMesId, ui.keepVisibleCount);
|
||||
const hiddenCount = ui.hideSummarized && range ? range.end + 1 : 0;
|
||||
|
||||
postToFrame({
|
||||
type: "SUMMARY_BASE_DATA",
|
||||
@@ -1347,11 +1379,7 @@ async function handleFrameMessage(event) {
|
||||
}
|
||||
|
||||
case "TOGGLE_HIDE_SUMMARIZED": {
|
||||
const store = getSummaryStore();
|
||||
if (!store) break;
|
||||
|
||||
store.hideSummarizedHistory = !!data.enabled;
|
||||
saveSummaryStore();
|
||||
setHideUiSettings({ hideSummarized: !!data.enabled });
|
||||
|
||||
(async () => {
|
||||
if (data.enabled) {
|
||||
@@ -1364,21 +1392,19 @@ async function handleFrameMessage(event) {
|
||||
}
|
||||
|
||||
case "UPDATE_KEEP_VISIBLE": {
|
||||
const store = getSummaryStore();
|
||||
if (!store) break;
|
||||
|
||||
const oldCount = store.keepVisibleCount ?? 3;
|
||||
const newCount = Math.max(0, Math.min(50, parseInt(data.count) || 3));
|
||||
const oldCount = getHideUiSettings().keepVisibleCount;
|
||||
const parsedCount = Number.parseInt(data.count, 10);
|
||||
const newCount = Number.isFinite(parsedCount) ? Math.max(0, Math.min(50, parsedCount)) : 6;
|
||||
if (newCount === oldCount) break;
|
||||
|
||||
store.keepVisibleCount = newCount;
|
||||
saveSummaryStore();
|
||||
setHideUiSettings({ keepVisibleCount: newCount });
|
||||
|
||||
(async () => {
|
||||
if (store.hideSummarizedHistory) {
|
||||
if (getHideUiSettings().hideSummarized) {
|
||||
await applyHideState();
|
||||
}
|
||||
const { chat } = getContext();
|
||||
const store = getSummaryStore();
|
||||
await sendFrameBaseData(store, Array.isArray(chat) ? chat.length : 0);
|
||||
})();
|
||||
break;
|
||||
@@ -1452,7 +1478,7 @@ async function handleChatChanged() {
|
||||
|
||||
const store = getSummaryStore();
|
||||
|
||||
if (store?.hideSummarizedHistory) {
|
||||
if (getHideUiSettings().hideSummarized) {
|
||||
await applyHideState();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user