fix(panel): memory list layout and formatted metrics

Made-with: Cursor
This commit is contained in:
Youzini-afk
2026-04-06 16:10:03 +08:00
parent cacbc12e2c
commit 76b5116798
2 changed files with 124 additions and 34 deletions

View File

@@ -1069,7 +1069,7 @@ function _refreshMemoryBrowser() {
const fragment = document.createDocumentFragment();
nodes.slice(0, 100).forEach((node) => {
const name = getNodeDisplayName(node);
const snippet = _getNodeSnippet(node);
const snippetText = _getNodeSnippet(node);
const li = document.createElement("li");
li.className = "bme-memory-item";
li.dataset.nodeId = String(node.id || "");
@@ -1077,40 +1077,59 @@ function _refreshMemoryBrowser() {
const badge = document.createElement("span");
badge.className = `bme-type-badge ${_safeCssToken(node.type)}`;
badge.textContent = _typeLabel(node.type);
li.appendChild(badge);
const scopeBadge = document.createElement("span");
scopeBadge.className = "bme-type-badge";
scopeBadge.textContent = buildScopeBadgeText(node.scope);
li.appendChild(scopeBadge);
const content = document.createElement("div");
const title = document.createElement("div");
title.className = "bme-memory-name";
title.textContent = name;
const badgesWrap = document.createElement("div");
badgesWrap.className = "bme-memory-badges";
badgesWrap.append(badge, scopeBadge);
const body = document.createElement("div");
body.className = "bme-memory-content";
body.textContent = snippet;
body.className = "bme-memory-body";
const titleEl = document.createElement("div");
titleEl.className = "bme-memory-name";
titleEl.textContent = name;
const snippetEl = document.createElement("div");
snippetEl.className = "bme-memory-content";
snippetEl.textContent = snippetText;
const meta = document.createElement("div");
meta.className = "bme-memory-meta";
["imp", "acc", "seq"].forEach((key, index) => {
const span = document.createElement("span");
span.textContent =
index === 0
? `imp: ${node.importance || 5}`
: index === 1
? `acc: ${node.accessCount || 0}`
: `seq: ${node.seqRange?.[1] ?? node.seq ?? 0}`;
meta.appendChild(span);
});
const impSpan = document.createElement("span");
impSpan.className = "bme-memory-metric";
impSpan.textContent = `重要度 ${_formatMemoryMetricNumber(node.importance, {
fallback: 5,
maxFrac: 2,
})}`;
const accSpan = document.createElement("span");
accSpan.className = "bme-memory-metric";
accSpan.textContent = `访问 ${_formatMemoryInt(node.accessCount, 0)}`;
const seqSpan = document.createElement("span");
seqSpan.className = "bme-memory-metric";
seqSpan.textContent = `序列 ${_formatMemoryInt(
node.seqRange?.[1] ?? node.seq,
0,
)}`;
meta.append(impSpan, accSpan, seqSpan);
const regionMeta = _buildScopeMetaText(node);
if (regionMeta) {
const scopeSpan = document.createElement("span");
scopeSpan.className = "bme-memory-regionline";
scopeSpan.textContent = regionMeta;
meta.appendChild(scopeSpan);
}
content.append(title, body, meta);
li.appendChild(content);
body.append(titleEl, snippetEl, meta);
li.append(badgesWrap, body);
fragment.appendChild(li);
});
listEl.replaceChildren(fragment);
@@ -5548,12 +5567,31 @@ function _buildScopeMetaText(node) {
parts.push(
`${scope.ownerType === "user" ? "用户 POV" : "角色 POV"}: ${scope.ownerName || scope.ownerId || "未命名"}`,
);
} else {
parts.push("客观层");
}
const regionLine = buildRegionLine(scope);
if (regionLine) parts.push(regionLine);
return parts.join(" | ");
return parts.join(" · ");
}
/** 记忆列表等指标:避免浮点误差打出 9.499999999999998 */
function _formatMemoryMetricNumber(value, { fallback = 0, maxFrac = 2 } = {}) {
const x =
value === undefined || value === null || value === ""
? Number(fallback)
: Number(value);
if (!Number.isFinite(x)) return "—";
const rounded = Number.parseFloat(x.toFixed(maxFrac));
if (Object.is(rounded, -0)) return "0";
return String(rounded);
}
function _formatMemoryInt(value, fallback = 0) {
const x =
value === undefined || value === null || value === ""
? Number(fallback)
: Number(value);
if (!Number.isFinite(x)) return "—";
return String(Math.trunc(x));
}
function _typeLabel(type) {

View File

@@ -710,6 +710,7 @@
/* --- Memory Browser Tab --- */
.bme-search-bar {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 10px;
}
@@ -743,6 +744,8 @@
font-size: 11px;
color: var(--bme-on-surface);
outline: none;
min-width: 7rem;
flex: 1 1 8rem;
}
.bme-memory-list {
@@ -751,13 +754,40 @@
margin: 0;
}
.bme-memory-badges {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
flex-shrink: 0;
max-width: min(11rem, 42%);
}
.bme-memory-badges .bme-type-badge {
margin-top: 0;
max-width: 100%;
white-space: normal;
word-break: break-word;
line-height: 1.25;
text-align: left;
}
.bme-memory-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
}
.bme-memory-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px;
border-radius: 4px;
margin-bottom: 3px;
gap: 10px;
padding: 10px 8px;
border-radius: 6px;
margin-bottom: 4px;
cursor: pointer;
transition: background 0.1s;
border: 1px solid transparent;
@@ -777,28 +807,50 @@
font-size: 12px;
font-weight: 600;
color: var(--bme-on-surface);
line-height: 1.35;
word-break: break-word;
overflow-wrap: anywhere;
}
.bme-memory-content {
font-size: 11px;
color: var(--bme-on-surface-dim);
margin-top: 2px;
line-height: 1.3;
line-height: 1.45;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
overflow-wrap: anywhere;
}
.bme-memory-meta {
display: flex;
gap: 8px;
margin-top: 4px;
flex-wrap: wrap;
align-items: baseline;
gap: 6px 10px;
margin-top: 2px;
font-size: 10px;
color: var(--bme-on-surface-dim);
}
.bme-memory-metric {
flex: 0 0 auto;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.bme-memory-regionline {
flex: 1 1 100%;
min-width: 0;
white-space: normal;
line-height: 1.35;
opacity: 0.92;
word-break: break-word;
overflow-wrap: anywhere;
}
/* --- Injection Preview Tab --- */
.bme-injection-preview {
font-family: "Cascadia Code", "Fira Code", monospace;