diff --git a/README.md b/README.md index 064c5d2..5b4bfcc 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ ST-BME 的记忆不是扁平的——它模拟了真实的认知分层: | ------ | ------ | ------ | | 向量预筛 Top-K | 20 | 向量预筛阶段最多保留多少个候选 | | LLM 精排候选池 | 30 | 进入 LLM 精排阶段前的候选池大小 | -| LLM 最终选择上限 | 8 | LLM 精排后最多保留多少条记忆 | +| LLM 最终选择上限 | 12 | LLM 精排后最多保留多少条记忆 | | 图扩散 Top-K | 100 | 图扩散阶段最多保留多少个候选 | | 注入深度 | 9999 | 当前走 IN_CHAT@Depth,数值越大越靠前插入 | | 多意图拆分 | 开 | 自动识别用户消息中的多个意图 | diff --git a/runtime/settings-defaults.js b/runtime/settings-defaults.js index b9b54ca..71e6cf2 100644 --- a/runtime/settings-defaults.js +++ b/runtime/settings-defaults.js @@ -37,7 +37,7 @@ export const defaultSettings = { worldInfoFilterMode: "default", worldInfoFilterCustomKeywords: "", recallTopK: 20, - recallMaxNodes: 8, + recallMaxNodes: 12, recallEnableLLM: true, recallEnableVectorPrefilter: true, recallEnableGraphDiffusion: true, diff --git a/style.css b/style.css index d4de447..e076a6b 100644 --- a/style.css +++ b/style.css @@ -1203,6 +1203,32 @@ border-top: 1px solid var(--bme-border); } +.bme-timeline-entry__line, +.bme-timeline-entry__empty { + font-size: 11px; + color: var(--bme-on-surface-dim); + line-height: 1.55; +} + +.bme-timeline-entry__line + .bme-timeline-entry__line { + margin-top: 6px; +} + +.bme-timeline-entry__preview { + margin-top: 10px; + padding: 10px 12px; + border-radius: 10px; + border: 1px solid var(--bme-border); + background: rgba(255, 255, 255, 0.035); + color: var(--bme-on-surface-dim); + font-size: 11px; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + max-height: 220px; + overflow: auto; +} + .bme-timeline-entry.is-collapsed .bme-timeline-entry__detail { display: none; } diff --git a/tests/default-settings.mjs b/tests/default-settings.mjs index 0bdaebd..b8e9339 100644 --- a/tests/default-settings.mjs +++ b/tests/default-settings.mjs @@ -9,7 +9,7 @@ assert.equal(defaultSettings.extractContextTurns, 2); assert.equal(defaultSettings.extractActionMode, "pending"); assert.equal(defaultSettings.extractAutoDelayLatestAssistant, false); assert.equal(defaultSettings.recallTopK, 20); -assert.equal(defaultSettings.recallMaxNodes, 8); +assert.equal(defaultSettings.recallMaxNodes, 12); assert.equal(defaultSettings.recallEnableVectorPrefilter, true); assert.equal(defaultSettings.recallEnableGraphDiffusion, true); assert.equal(defaultSettings.recallDiffusionTopK, 100); diff --git a/ui/panel.js b/ui/panel.js index a463ad4..dac4947 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -1566,6 +1566,56 @@ function _refreshTaskPipelineOverview() { // ---------- Task Timeline ---------- +function _getTaskTimelineEntrySeverity(entry = {}) { + const explicitLevel = String(entry?.level || "").trim().toLowerCase(); + if (explicitLevel) return explicitLevel; + + const status = String(entry?.status || "").trim().toLowerCase(); + if (status.includes("error") || status.includes("fail")) return "error"; + if (status.includes("warn")) return "warn"; + return "info"; +} + +function _buildTaskTimelineDetailState(entry = {}) { + const detailLines = []; + const legacyDetail = String(entry?.text || entry?.meta || "").trim(); + const routeInfo = _formatMonitorRouteInfo(entry); + const governanceLines = _summarizeMonitorGovernance(entry); + const messageCount = Array.isArray(entry?.messages) ? entry.messages.length : 0; + const rawPreviewText = _buildMonitorMessagesPreview(entry?.messages || []); + const previewText = + rawPreviewText.length > 480 + ? `${rawPreviewText.slice(0, 480)}\n\n...(详情已截断)` + : rawPreviewText; + + if (legacyDetail) { + detailLines.push(legacyDetail); + } + if (routeInfo && routeInfo !== "未记录路由信息") { + detailLines.push(`路由: ${routeInfo}`); + } + for (const line of governanceLines) { + const normalized = String(line || "").trim(); + if (normalized) detailLines.push(normalized); + } + if (messageCount > 0) { + detailLines.push(`消息快照: ${messageCount} 条`); + } + + const uniqueLines = []; + for (const line of detailLines) { + if (!uniqueLines.includes(line)) { + uniqueLines.push(line); + } + } + + return { + detailLines: uniqueLines, + previewText, + hasRenderableDetail: uniqueLines.length > 0 || Boolean(previewText), + }; +} + function _refreshTaskTimeline() { const el = document.getElementById("bme-task-timeline"); if (!el) return; @@ -1581,14 +1631,22 @@ function _refreshTaskTimeline() { const entries = timeline.slice().reverse().map((entry, idx) => { const t = entry.updatedAt ? new Date(entry.updatedAt).toLocaleTimeString() : ""; - const title = entry.taskType || entry.stage || "task"; + const taskType = String(entry?.taskType || entry?.stage || "task"); + const title = entry?.taskType + ? _getMonitorTaskTypeLabel(taskType) + : taskType; const statusText = entry.status || ""; const durationMs = entry.durationMs; - const durationStr = typeof durationMs === "number" ? `${(durationMs / 1000).toFixed(1)}s` : ""; - const detail = entry.text || entry.meta || ""; - const level = entry.level || "info"; + const durationStr = _formatDurationMs(durationMs); + const { detailLines, previewText, hasRenderableDetail } = + _buildTaskTimelineDetailState(entry); + const level = _getTaskTimelineEntrySeverity(entry); const levelIcon = level === "error" ? "circle-exclamation" : level === "warn" ? "triangle-exclamation" : "circle-check"; const levelColor = level === "error" ? "#e74c3c" : level === "warn" ? "#f39c12" : "#2ecc71"; + const metaParts = [ + durationStr && durationStr !== "—" ? durationStr : "", + t, + ].filter(Boolean); const substages = Array.isArray(entry.substages) ? entry.substages.map((sub) => `