This commit is contained in:
GH Action - Upstream Sync
2026-02-24 06:00:53 +00:00
9 changed files with 694 additions and 134 deletions

View File

@@ -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 {

View File

@@ -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 };

View File

@@ -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...

View File

@@ -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;
}
// ─────────────────────────────────────────────────────────────────────────────
// EvidenceGroupper-floorN个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
);

View File

@@ -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 });
};

View File

@@ -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,
@@ -951,10 +951,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({
@@ -966,8 +997,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,
});
}
@@ -1042,7 +1073,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();
@@ -1050,7 +1082,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}`);
@@ -1150,9 +1182,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",
@@ -1348,11 +1380,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) {
@@ -1365,21 +1393,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;
@@ -1453,7 +1479,7 @@ async function handleChatChanged() {
const store = getSummaryStore();
if (store?.hideSummarizedHistory) {
if (getHideUiSettings().hideSummarized) {
await applyHideState();
}