Improve story summary logging

This commit is contained in:
2026-02-01 15:07:06 +08:00
parent 5dd9fb6f97
commit 4eeebdd935
5 changed files with 650 additions and 663 deletions

View File

@@ -36,7 +36,7 @@ const RECENT_ORPHAN_MAX = 5000; // [待整理] 独立预算
const TOTAL_BUDGET_MAX = 15000; // 总预算(用于日志显示) const TOTAL_BUDGET_MAX = 15000; // 总预算(用于日志显示)
const L3_MAX = 2000; const L3_MAX = 2000;
const ARCS_MAX = 1500; const ARCS_MAX = 1500;
const TOP_N_STAR = 5; // 相似度前N条加⭐ const TOP_N_STAR = 5; // 匹配度前N条加⭐
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// 工具函数 // 工具函数
@@ -179,84 +179,64 @@ function formatInjectionLog(stats, details, recentOrphanStats = null) {
const pct = (n, d) => (d > 0 ? Math.round((n / d) * 100) : 0); const pct = (n, d) => (d > 0 ? Math.round((n / d) * 100) : 0);
const lines = [ const lines = [
"", '',
"╔══════════════════════════════════════════════════════════════╗", '\u250c' + '\u2500'.repeat(61) + '\u2510',
"║ Prompt 装配报告 ║", '\u2502 \u3010\u88c5\u914d\u7edf\u8ba1\u3011 \u2502',
"╠══════════════════════════════════════════════════════════════╣", '\u2514' + '\u2500'.repeat(61) + '\u2518',
` 总预算: ${stats.budget.max} tokens`, ` \u603b\u9884\u7b97: ${stats.budget.max} tokens | \u5df2\u4f7f\u7528: ${stats.budget.used} tokens (${pct(stats.budget.used, stats.budget.max)}%)`,
`║ 已使用: ${stats.budget.used} tokens (${pct(stats.budget.used, stats.budget.max)}%)`, '',
`║ 剩余: ${stats.budget.max - stats.budget.used} tokens`,
"╚══════════════════════════════════════════════════════════════╝",
"",
]; ];
// 世界状态 // [1] World constraints
lines.push("┌─────────────────────────────────────────────────────────────┐"); lines.push(' [1] \u4e16\u754c\u7ea6\u675f (\u4e0a\u9650 2000)');
lines.push("│ [1] 世界约束 (上限 2000) │"); lines.push(` \u9009\u5165: ${stats.world.count} \u6761 | \u6d88\u8017: ${stats.world.tokens} tokens`);
lines.push("└─────────────────────────────────────────────────────────────┘"); lines.push('');
lines.push(` 注入: ${stats.world.count} 条 | ${stats.world.tokens} tokens`);
lines.push("");
// 核心经历 + 过往背景 // [2] Core + background events
lines.push("┌─────────────────────────────────────────────────────────────┐"); lines.push(' [2] \u6838\u5fc3\u7ecf\u5386 + \u8fc7\u5f80\u80cc\u666f');
lines.push("│ [2] 核心经历 + 过往背景(含证据) │"); lines.push(` \u4e8b\u4ef6: ${stats.events.selected} \u6761 | \u6d88\u8017: ${stats.events.tokens} tokens`);
lines.push("└─────────────────────────────────────────────────────────────┘");
lines.push(` 选入: ${stats.events.selected} 条 | 事件本体: ${stats.events.tokens} tokens`);
lines.push(` 挂载证据: ${stats.evidence.attached} 条 | 证据: ${stats.evidence.tokens} tokens`);
lines.push(` 核心: ${details.directCount || 0} | 过往: ${details.similarCount || 0}`);
if (details.eventList?.length) {
lines.push(" ────────────────────────────────────────");
details.eventList.slice(0, 20).forEach((ev, i) => {
const type = ev.isDirect ? "核心" : "过往";
const hasE = ev.hasEvidence ? " +E" : "";
const title = (ev.title || "(无标题)").slice(0, 32);
lines.push(` ${String(i + 1).padStart(2)}. [${type}${hasE}] ${title} (${ev.tokens}tok)`);
});
if (details.eventList.length > 20) lines.push(` ... 还有 ${details.eventList.length - 20}`);
}
lines.push("");
// 远期片段 const l0EvidenceCount = details.eventList?.filter(e => e.hasL0Evidence)?.length || 0;
lines.push("┌─────────────────────────────────────────────────────────────┐"); const l1EvidenceCount = (stats.evidence.attached || 0) - l0EvidenceCount;
lines.push("│ [3] 远期片段(已总结范围) │"); lines.push(` \u8bc1\u636e: ${stats.evidence.attached} \u6761 (L0: ${l0EvidenceCount}, L1: ${l1EvidenceCount}) | \u6d88\u8017: ${stats.evidence.tokens} tokens`);
lines.push("└─────────────────────────────────────────────────────────────┘"); lines.push(` \u6838\u5fc3: ${details.directCount || 0} \u6761 | \u8fc7\u5f80: ${details.similarCount || 0} \u6761`);
lines.push(` 注入: ${stats.orphans.injected} 条 | ${stats.orphans.tokens} tokens`); lines.push('');
lines.push("");
// 待整理 // [3] Long-term chunks
lines.push("┌─────────────────────────────────────────────────────────────┐"); const l0OrphanCount = stats.orphans.l0Count || 0;
lines.push("│ [4] 待整理(未总结范围,独立预算 5000 │"); const l1OrphanCount = (stats.orphans.injected || 0) - l0OrphanCount;
lines.push("└─────────────────────────────────────────────────────────────┘"); lines.push(' [3] \u8fdc\u671f\u7247\u6bb5 (\u5df2\u603b\u7ed3\u8303\u56f4)');
lines.push(` 注入: ${recentOrphanStats?.injected || 0} 条 | ${recentOrphanStats?.tokens || 0} tokens`); lines.push(` \u9009\u5165: ${stats.orphans.injected} \u6761 (L0: ${l0OrphanCount}, L1: ${l1OrphanCount}) | \u6d88\u8017: ${stats.orphans.tokens} tokens`);
lines.push(` 楼层范围: ${recentOrphanStats?.floorRange || "N/A"}`); lines.push('');
lines.push("");
lines.push("┌─────────────────────────────────────────────────────────────┐"); // [4] Recent orphans
lines.push("│ [5] 人物弧光(上限 1500 │"); lines.push(' [4] \u5f85\u6574\u7406 (\u72ec\u7acb\u9884\u7b97 5000)');
lines.push("└─────────────────────────────────────────────────────────────┘"); lines.push(` \u9009\u5165: ${recentOrphanStats?.injected || 0} \u6761 | \u6d88\u8017: ${recentOrphanStats?.tokens || 0} tokens`);
lines.push(` 注入: ${stats.arcs.count} 条 | ${stats.arcs.tokens} tokens`); lines.push(` \u697c\u5c42: ${recentOrphanStats?.floorRange || 'N/A'}`);
lines.push(""); lines.push('');
// 预算条形图 // [5] Arcs
lines.push("┌─────────────────────────────────────────────────────────────┐"); lines.push(' [5] \u4eba\u7269\u5f27\u5149 (\u4e0a\u9650 1500)');
lines.push("│ 【预算分布】 │"); lines.push(` \u9009\u5165: ${stats.arcs.count} \u6761 | \u6d88\u8017: ${stats.arcs.tokens} tokens`);
lines.push("└─────────────────────────────────────────────────────────────┘"); lines.push('');
// Budget bar
lines.push(' \u3010\u9884\u7b97\u5206\u5e03\u3011');
const total = stats.budget.max; const total = stats.budget.max;
const bar = (tokens, label) => { const bar = (tokens, label) => {
const width = Math.round((tokens / total) * 40); const width = Math.round((tokens / total) * 30);
const pctStr = pct(tokens, total) + "%"; const pctStr = pct(tokens, total) + '%';
return ` ${label.padEnd(6)} ${"█".repeat(width).padEnd(40)} ${String(tokens).padStart(5)} (${pctStr})`; return ` ${label.padEnd(6)} ${'\u2588'.repeat(width).padEnd(30)} ${String(tokens).padStart(5)} (${pctStr})`;
}; };
lines.push(bar(stats.world.tokens, "约束")); lines.push(bar(stats.world.tokens, '\u7ea6\u675f'));
lines.push(bar(stats.events.tokens, "经历")); lines.push(bar(stats.events.tokens + stats.evidence.tokens, '\u7ecf\u5386'));
lines.push(bar(stats.evidence.tokens, "证据")); lines.push(bar(stats.orphans.tokens, '\u8fdc\u671f'));
lines.push(bar(stats.orphans.tokens, "远期")); lines.push(bar(recentOrphanStats?.tokens || 0, '\u5f85\u6574\u7406'));
lines.push(bar(recentOrphanStats?.tokens || 0, "待整理")); lines.push(bar(stats.arcs.tokens, '\u5f27\u5149'));
lines.push(bar(stats.arcs.tokens, "弧光")); lines.push(bar(stats.budget.max - stats.budget.used, '\u5269\u4f59'));
lines.push(bar(stats.budget.max - stats.budget.used, "剩余")); lines.push('');
lines.push("");
return lines.join("\n"); return lines.join('\n');
} }
// 重写事件文本里的序号前缀:把 “{idx}. ” 或 “{idx}.【...】” 的 idx 替换 // 重写事件文本里的序号前缀:把 “{idx}. ” 或 “{idx}.【...】” 的 idx 替换
function renumberEventText(text, newIndex) { function renumberEventText(text, newIndex) {
@@ -468,7 +448,7 @@ async function buildVectorPrompt(store, recallResult, causalById, queryEntities
return lines.join("\n"); return lines.join("\n");
} }
// 候选按相似度从高到低(保证高分优先拥有证据) // 候选按匹配度从高到低(保证高分优先拥有证据)
const candidates = [...recalledEvents].sort((a, b) => (b.similarity || 0) - (a.similarity || 0)); const candidates = [...recalledEvents].sort((a, b) => (b.similarity || 0) - (a.similarity || 0));
const selectedDirect = []; // { event, text, tokens, chunk, hasEvidence } const selectedDirect = []; // { event, text, tokens, chunk, hasEvidence }

View File

@@ -2677,3 +2677,30 @@ h1 span {
font-size: .8125rem; font-size: .8125rem;
line-height: 1.8; line-height: 1.8;
} }
/* 调试日志区域手机适配 */
@media (max-width: 768px) {
#recall-log-content {
font-size: 10px;
padding: 8px;
overflow-x: hidden; /* 禁止横向滚动 */
word-break: break-all; /* 强制换行 */
white-space: pre-wrap; /* 保留换行但允许自动换行 */
}
.debug-log-viewer {
font-size: 10px;
overflow-x: hidden;
word-break: break-all;
white-space: pre-wrap;
}
}
@media (max-width: 480px) {
#recall-log-content,
.debug-log-viewer {
font-size: 9px;
line-height: 1.4;
padding: 6px;
}
}

View File

@@ -109,6 +109,26 @@ let vectorAbortController = null;
let lastSentUserMessage = null; let lastSentUserMessage = null;
let lastSentTimestamp = 0; let lastSentTimestamp = 0;
function captureUserInput() {
const text = $("#send_textarea").val();
if (text?.trim()) {
lastSentUserMessage = text.trim();
lastSentTimestamp = Date.now();
}
}
function onSendPointerdown(e) {
if (e.target?.closest?.("#send_but")) {
captureUserInput();
}
}
function onSendKeydown(e) {
if (e.key === "Enter" && !e.shiftKey && e.target?.closest?.("#send_textarea")) {
captureUserInput();
}
}
let hideApplyTimer = null; let hideApplyTimer = null;
const HIDE_APPLY_DEBOUNCE_MS = 250; const HIDE_APPLY_DEBOUNCE_MS = 250;
@@ -1483,6 +1503,10 @@ function registerEvents() {
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));
// 用户输入捕获(原生捕获阶段)
document.addEventListener("pointerdown", onSendPointerdown, true);
document.addEventListener("keydown", onSendKeydown, true);
// 注入链路 // 注入链路
eventSource.on(event_types.GENERATION_STARTED, handleGenerationStarted); eventSource.on(event_types.GENERATION_STARTED, handleGenerationStarted);
eventSource.on(event_types.GENERATION_STOPPED, clearExtensionPrompt); eventSource.on(event_types.GENERATION_STOPPED, clearExtensionPrompt);
@@ -1497,6 +1521,9 @@ function unregisterEvents() {
hideOverlay(); hideOverlay();
clearExtensionPrompt(); clearExtensionPrompt();
document.removeEventListener("pointerdown", onSendPointerdown, true);
document.removeEventListener("keydown", onSendKeydown, true);
} }
// ═══════════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════════

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,7 @@ export async function searchStateAtoms(queryVector, vectorConfig) {
const atoms = getStateAtoms(); const atoms = getStateAtoms();
const atomMap = new Map(atoms.map(a => [a.atomId, a])); const atomMap = new Map(atoms.map(a => [a.atomId, a]));
// 计算相似 // 计算匹配
const scored = stateVectors const scored = stateVectors
.map(sv => { .map(sv => {
const atom = atomMap.get(sv.atomId); const atom = atomMap.get(sv.atomId);
@@ -92,8 +92,8 @@ export function buildL0FloorBonus(l0Results, bonusFactor = 0.10) {
const floorBonus = new Map(); const floorBonus = new Map();
for (const r of l0Results || []) { for (const r of l0Results || []) {
// 每个楼层只加一次,取最高相似度对应的 bonus // 每个楼层只加一次,取最高匹配度对应的 bonus
// 简化处理:统一加 bonusFactor不区分相似度高低 // 简化处理:统一加 bonusFactor不区分匹配度高低
if (!floorBonus.has(r.floor)) { if (!floorBonus.has(r.floor)) {
floorBonus.set(r.floor, bonusFactor); floorBonus.set(r.floor, bonusFactor);
} }
@@ -132,13 +132,13 @@ export function stateToVirtualChunks(l0Results) {
/** /**
* 合并 L0 和 L1 chunks每楼层最多保留 limit 条 * 合并 L0 和 L1 chunks每楼层最多保留 limit 条
* @param {Array} l0Chunks - 虚拟 chunks已按相似度排序) * @param {Array} l0Chunks - 虚拟 chunks已按匹配度排序)
* @param {Array} l1Chunks - 真实 chunks已按相似度排序) * @param {Array} l1Chunks - 真实 chunks已按匹配度排序)
* @param {number} limit - 每楼层上限 * @param {number} limit - 每楼层上限
* @returns {Array} 合并后的 chunks * @returns {Array} 合并后的 chunks
*/ */
export function mergeAndSparsify(l0Chunks, l1Chunks, limit = 2) { export function mergeAndSparsify(l0Chunks, l1Chunks, limit = 2) {
// 合并并按相似度排序 // 合并并按匹配度排序
const all = [...(l0Chunks || []), ...(l1Chunks || [])] const all = [...(l0Chunks || []), ...(l1Chunks || [])]
.sort((a, b) => b.similarity - a.similarity); .sort((a, b) => b.similarity - a.similarity);
@@ -153,7 +153,7 @@ export function mergeAndSparsify(l0Chunks, l1Chunks, limit = 2) {
} }
} }
// 扁平化并保持相似度排序 // 扁平化并保持匹配度排序
return Array.from(byFloor.values()) return Array.from(byFloor.values())
.flat() .flat()
.sort((a, b) => b.similarity - a.similarity); .sort((a, b) => b.similarity - a.similarity);