diff --git a/ui/panel.js b/ui/panel.js index 4dec443..84bbfe6 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -1538,13 +1538,47 @@ function _refreshTaskMemoryBrowser() { if (!el) return; const graph = _getGraph?.(); - if (!graph) { + const loadInfo = _getGraphPersistenceSnapshot(); + if (!graph || !_canRenderGraphData(loadInfo)) { el.innerHTML = '
图谱未加载
'; return; } - const nodes = graph.nodes || []; - const sorted = nodes.slice().sort((a, b) => (b.importance || 0) - (a.importance || 0)); + const currentQuery = String(document.getElementById("bme-task-memory-search")?.value || "") + .trim() + .toLowerCase(); + const currentFilter = document.getElementById("bme-task-memory-filter")?.value || "all"; + + let nodes = Array.isArray(graph.nodes) + ? graph.nodes.filter((node) => !node?.archived) + : []; + + if (currentFilter !== "all") { + nodes = nodes.filter((node) => _matchesMemoryFilter(node, currentFilter)); + } + + if (currentQuery) { + nodes = nodes.filter((node) => { + const name = getNodeDisplayName(node).toLowerCase(); + const snippet = _getNodeSnippet(node).toLowerCase(); + const fieldsText = JSON.stringify(node?.fields || {}).toLowerCase(); + return ( + name.includes(currentQuery) || + snippet.includes(currentQuery) || + fieldsText.includes(currentQuery) + ); + }); + } + + const sorted = nodes.slice().sort((a, b) => { + const importanceDiff = (b.importance || 5) - (a.importance || 5); + if (importanceDiff !== 0) return importanceDiff; + return (b.seqRange?.[1] ?? b.seq ?? 0) - (a.seqRange?.[1] ?? a.seq ?? 0); + }); + + if (!sorted.some((node) => node.id === currentSelectedMemoryNodeId)) { + currentSelectedMemoryNodeId = sorted[0]?.id || ""; + } const typeClass = (type) => { switch (type) { @@ -1557,27 +1591,25 @@ function _refreshTaskMemoryBrowser() { } }; - const typeLabel = (node) => { - if (node.scope === "characterPov") return "角色POV"; - if (node.scope === "userPov") return "用户POV"; - return node.type || "memory"; - }; - const listItems = sorted.map((node) => { const sel = node.id === currentSelectedMemoryNodeId ? "selected" : ""; - const preview = (node.description || "").slice(0, 80); + const preview = _getNodeSnippet(node); + const scopeBadge = buildScopeBadgeText(node.scope); + const metaText = _buildScopeMetaText(node); + const displayName = getNodeDisplayName(node); return `
- ${_escHtml(typeLabel(node))} + ${_escHtml(_typeLabel(node.type))} IMP: ${typeof node.importance === "number" ? node.importance.toFixed(1) : "—"}
-
${_escHtml(node.label || node.id)}
+
${_escHtml(displayName)}
${_escHtml(preview)}
- SEQ: ${node.lastSeq ?? "—"} - ${node.region ? `LOC: ${_escHtml(node.region)}` : ""} + ${_escHtml(scopeBadge)} + SEQ: ${_formatMemoryInt(node.seqRange?.[1] ?? node.seq, 0)}
+ ${metaText ? `
${_escHtml(metaText)}
` : ""}
`; }).join(""); @@ -1587,17 +1619,17 @@ function _refreshTaskMemoryBrowser() {
- +
@@ -1611,6 +1643,17 @@ function _refreshTaskMemoryBrowser() { `; _bindTaskMemoryListClick(); + + const searchInput = document.getElementById("bme-task-memory-search"); + const filterSelect = document.getElementById("bme-task-memory-filter"); + if (searchInput) { + let timer = null; + searchInput.addEventListener("input", () => { + clearTimeout(timer); + timer = setTimeout(() => _refreshTaskMemoryBrowser(), 180); + }); + } + filterSelect?.addEventListener("change", () => _refreshTaskMemoryBrowser()); } function _bindTaskMemoryListClick() { @@ -1632,32 +1675,55 @@ function _renderMemoryDetailPanel(node, graph) { if (!node) return '
选择左侧节点查看详情
'; const edges = (graph?.edges || []).filter((e) => e.source === node.id || e.target === node.id); + const fields = node?.fields || {}; + const detailSummary = _getNodeSnippet(node); + const scopeMeta = _buildScopeMetaText(node); + const scopeBadge = buildScopeBadgeText(node.scope); + const displayName = getNodeDisplayName(node); const badges = [ - node.type ? `${_escHtml(node.type)}` : "", - node.scope ? `${_escHtml(node.scope)}` : "", + node.type ? `${_escHtml(_typeLabel(node.type))}` : "", + scopeBadge ? `${_escHtml(scopeBadge)}` : "", node.archived ? 'ARCHIVED' : "", ].filter(Boolean).join(""); - const fields = [ + const detailFields = [ ["ID", node.id], - ["Owner", node.owner || "—"], - ["Region", node.region || "—"], - ["Importance", typeof node.importance === "number" ? node.importance.toFixed(2) : "—"], - ["Last Seq", node.lastSeq ?? "—"], - ["Created", node.createdAt || "—"], - ["Updated", node.updatedAt || "—"], + ["名称", displayName], + ["范围", scopeMeta || scopeBadge || "—"], + ["重要度", _formatMemoryMetricNumber(node.importance, { fallback: 5, maxFrac: 2 })], + ["访问次数", _formatMemoryInt(node.accessCount, 0)], + ["最后序列", _formatMemoryInt(node.seqRange?.[1] ?? node.seq, 0)], + ["创建时间", node.createdAt || "—"], + ["更新时间", node.updatedAt || "—"], ]; + const extraFieldRows = Object.entries(fields) + .filter(([key]) => !["embedding", "name", "title", "summary"].includes(key)) + .slice(0, 6) + .map(([key, value]) => { + let text = "—"; + if (typeof value === "string") { + text = value; + } else if (value !== undefined && value !== null) { + try { + text = JSON.stringify(value); + } catch { + text = String(value); + } + } + return [key, text]; + }); + return ` -
${_escHtml(node.label || node.id)}
+
${_escHtml(displayName)}
${badges}
-
${_escHtml(node.description || "无描述")}
+
${_escHtml(detailSummary || "无补充字段")}
- ${fields.map(([k, v]) => `
${_escHtml(k)}${_escHtml(String(v))}
`).join("")} + ${detailFields.concat(extraFieldRows).map(([k, v]) => `
${_escHtml(k)}${_escHtml(String(v))}
`).join("")}
${edges.length} 条连接 - recall ${node.recallCount ?? 0} + 访问 ${_formatMemoryInt(node.accessCount, 0)}
`; }