diff --git a/ui/panel.js b/ui/panel.js index 49ba57a..43d561f 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -2234,108 +2234,159 @@ function _refreshTaskPersistence() { const verboseDebugLabel = globalThis.__stBmeVerboseDebug === true ? "开启" : "关闭"; const projectionLabel = ps?.projectionState?.runtime?.status || ps?.projectionState?.persistent?.status || "—"; + const compactTargetLabel = (() => { + const target = ps.chatStateTarget; + if (!target || typeof target !== "object") return "未绑定"; + if (target.is_group === true) { + return `群聊 · ${String(target.id || "—")}`; + } + return `角色聊天 · ${String(target.file_name || "—")}`; + })(); + const mirrorLabel = + CACHE_MIRROR_LABELS[ps.cacheMirrorState] || ps.cacheMirrorState || "—"; + const acceptedSummaryLabel = + ps.pendingPersist === true + ? "待确认" + : ps.persistMismatchReason + ? "一致性异常" + : acceptedTierLabel !== "—" && acceptedTierLabel !== "无" + ? acceptedTierLabel + : ps.shadowSnapshotUsed + ? "仅恢复锚点" + : "未确认"; + const healthLabel = ps.pendingPersist === true + ? "等待正式持久化确认" + : ps.persistMismatchReason + ? _formatPersistMismatchReason(ps.persistMismatchReason) + : ps.blockedReason || (ps.loadState === "blocked" ? ps.reason : "") || "正常"; + const localEngineLabel = + ps.resolvedLocalStore + ? String(ps.resolvedLocalStore).replace(":", " / ") + : cacheTierLabel; + const sidecarSummaryLabel = + ps.hostProfile === "luker" + ? `rev ${manifestRevisionLabel} · ${journalStateLabel}` + : "—"; + const historyState = graph?.historyState || {}; + const summaryState = graph?.summaryState || {}; + const journalCount = Array.isArray(graph?.batchJournal) ? graph.batchJournal.length : 0; + const summaryCount = Array.isArray(summaryState?.entries) ? summaryState.entries.length : 0; + const activeSummaryCount = Array.isArray(summaryState?.activeEntryIds) + ? summaryState.activeEntryIds.length + : 0; + const processedFloorLabel = Number.isFinite(Number(historyState?.lastProcessedAssistantFloor)) + ? String(Number(historyState.lastProcessedAssistantFloor)) + : "—"; + const extractionCountLabel = Number.isFinite(Number(historyState?.extractionCount)) + ? String(Number(historyState.extractionCount)) + : "0"; + const activeRegionLabel = String( + historyState?.activeRegion || + historyState?.lastExtractedRegion || + "—", + ); + const dirtyFromLabel = Number.isFinite(Number(historyState?.historyDirtyFrom)) + ? String(Number(historyState.historyDirtyFrom)) + : "无"; - const kvs = [ - ["加载状态", loadStateLabel], - ["宿主档案", hostProfileLabel], - ["Chat Target", ps.chatStateTarget ? JSON.stringify(ps.chatStateTarget) : "—"], + const summaryPills = [ + `加载 · ${loadStateLabel}`, + `宿主 · ${hostProfileLabel}`, + `主存储 · ${primaryTierLabel}`, + `确认 · ${acceptedSummaryLabel}`, + ]; + const renderRows = (rows = []) => + rows + .filter(([, value]) => value !== null && value !== undefined && value !== "") + .map( + ([key, value]) => + `
${_escHtml(String(key))}${_escHtml(String(value))}
`, + ) + .join(""); + + const primaryRows = [ + ["当前状态", acceptedSummaryLabel], + ["健康状态", healthLabel], + ["Chat Target", compactTargetLabel], ["主 durable", primaryTierLabel], - ["当前 accepted", acceptedTierLabel], + ps.hostProfile === "luker" + ? ["Luker Sidecar", sidecarSummaryLabel] + : ["本地引擎", localEngineLabel], + ps.hostProfile === "luker" + ? ["本地缓存", `${cacheTierLabel} · ${mirrorLabel}`] + : ["恢复锚点", ps.shadowSnapshotUsed ? "影子快照已接管" : "无"], + ]; + + const runtimeRows = [ + ["图谱节点", String((graph.nodes || []).length)], + ["图谱边", String((graph.edges || []).length)], + ["批次日志", String(journalCount)], + ["提取次数", extractionCountLabel], + ["已处理楼层", processedFloorLabel], + ["总结条目", `${summaryCount}(活跃 ${activeSummaryCount})`], + ["当前区域", activeRegionLabel], + ["脏区起点", dirtyFromLabel], + ["运行版本", String(rs.graphRevision ?? "—")], + ]; + + const diagnosticRows = [ + ["宿主档案", hostProfileLabel], ["accepted by", ps.acceptedBy || "—"], - ["Sidecar 格式", sidecarFormatLabel], - ["Manifest rev", manifestRevisionLabel], - ["Journal", journalStateLabel], - ["Checkpoint rev", checkpointRevisionLabel], - ["本地缓存", cacheTierLabel], - ["缓存镜像", CACHE_MIRROR_LABELS[ps.cacheMirrorState] || ps.cacheMirrorState || "—"], - ["缓存落后", cacheLagLabel], - ["解析本地引擎", ps.resolvedLocalStore || "—"], + ["诊断层", STORAGE_TIER_LABELS[ps.persistDiagnosticTier] || ps.persistDiagnosticTier || "无"], + ["提交标记", ps.commitMarker ? "存在(诊断锚点)" : "无"], + ["版本号", ps.revision ?? "—"], ["本地格式", `v${Number(ps.localStoreFormatVersion || 0) || 1}`], ["本地迁移", ps.localStoreMigrationState || "—"], - ["版本号", ps.revision ?? "—"], - ["提交标记", ps.commitMarker ? "存在(诊断锚点)" : "无"], - ["Verbose Debug", verboseDebugLabel], ["轻量模式", ps.lightweightHostMode ? "开启" : "关闭"], + ["Verbose Debug", verboseDebugLabel], ["Luker Hook", ps.lastHookPhase || "—"], ["Projection", projectionLabel], ["Rescan 原因", ps.lastRequestRescanReason || "—"], ["忽略变更", ps.lastIgnoredMutationEvent || "—"], - ["诊断层", STORAGE_TIER_LABELS[ps.persistDiagnosticTier] || ps.persistDiagnosticTier || "无"], - ["阻塞原因", ps.blockedReason || ps.reason || "—"], ["影子快照", ps.shadowSnapshotUsed ? "已使用" : "未使用"], ["OPFS 写锁", opfsLockLabel], ["OPFS WAL", `${Number(ps.opfsWalDepth || 0)} 条 / ${Number(ps.opfsPendingBytes || 0)} B`], ["OPFS 压实", opfsCompactionLabel], ["远端同步格式", `v${Number(ps.remoteSyncFormatVersion || 0) || 1}`], ]; - - const kvHtml = kvs.map(([k, v]) => `
${_escHtml(k)}${_escHtml(String(v))}
`).join(""); - - const journalCount = Array.isArray(rs.historyState?.batchJournal) ? rs.historyState.batchJournal.length : 0; - const secondaryKvs = [ - ["图谱节点", String((graph.nodes || []).length)], - ["图谱边", String((graph.edges || []).length)], - ["批次日志", String(journalCount)], - ["运行版本", String(rs.graphRevision ?? "—")], - ]; - const secondaryHtml = secondaryKvs.map(([k, v]) => `
${_escHtml(k)}${_escHtml(v)}
`).join(""); - - const guidePairs = [ - ["加载状态", "记忆图谱在当前聊天中的加载进度。\"已加载\" 表示正常运行。"], - ["宿主档案", "当前运行环境。Luker 会把聊天侧车当主 durable 存储,其它宿主仍以本地存储为主。"], - ["Chat Target", "Luker 当前绑定的 chat-state target。branch 派生和后台任务应显式指向它,而不是依赖当前聊天。"], - ["主 durable", "当前宿主下真正负责 accepted 的主存储层。"], - ["当前 accepted", "最近一次已确认持久化最终落在哪一层。"], - ["accepted by", "本批最近一次 accepted 是由哪一层确认的。"], - ["Sidecar 格式", "Luker 主 sidecar 的格式版本。v2 代表 manifest + journal + checkpoint。"], - ["Manifest rev", "Luker 主 sidecar manifest 当前确认的 head revision。"], - ["Journal", "Luker sidecar 未压实 journal 的条目数和累计字节数。"], - ["Checkpoint rev", "Luker sidecar 最近一次压实基线的 revision。"], - ["本地缓存", "主存储之外的本地缓存层。Luker 下这里通常是 IndexedDB 或 OPFS。"], - ["缓存镜像", "本地缓存 mirror 的当前状态。失败不会自动等价为主持久化失败。"], - ["缓存落后", "Luker manifest revision 与本地缓存 revision 的差值。0 表示本地缓存已追平。"], - ["解析本地引擎", "当前模式最终解析到的本地引擎,例如 auto 解析成 OPFS 或 IndexedDB。"], - ["本地格式", "当前本地存储格式版本。OPFS v2 代表分片基线 + WAL。"], - ["本地迁移", "当前本地存储迁移状态,例如 idle / promoting。"], - ["版本号", "图谱修订号,每次写入操作自增。用于检测并发冲突。"], - ["提交标记", "聊天元数据中的诊断锚点,只用于对账与修复建议,不再单独代表 accepted。"], - ["Verbose Debug", "是否抓取完整调试载荷。默认关闭,仅保留轻量摘要。"], - ["轻量模式", "Luker Android/WebView 或移动端下默认启用,主动收紧调试和运行态缓存。"], - ["Luker Hook", "最近一次命中的 Luker 正式 generation hook 阶段。"], - ["Projection", "当前 runtime / persistent projection 的轻量状态。runtime projection 会在生成结束后回落为空闲。"], - ["Rescan 原因", "如果当前轮次通过 runtime projection 请求了 world-info rescan,这里会显示最后一次原因。"], - ["忽略变更", "最近一次被按 MESSAGE_UPDATED 轻刷新降级处理的消息变更。"], - ["诊断层", "最近一次仅作诊断/恢复用途的层级,例如影子快照或完整 metadata。"], - ["阻塞原因", "如果加载被阻塞,这里显示具体原因。\"—\" 表示未阻塞。"], - ["影子快照", "是否在启动时使用了上次会话留下的影子快照来加速加载。"], - ["OPFS 写锁", "OPFS 本地存储的串行写状态。活跃表示当前有写任务排队或执行中。"], - ["OPFS WAL", "当前尚未被压实进基线分片的日志条目数和累计字节数。"], - ["OPFS 压实", "OPFS 基线压实状态。pending 表示达到阈值后等待后台压实。"], - ["远端同步格式", "当前自动同步使用的远端存储格式版本。v2 代表 manifest + chunk。"], - ["图谱节点 / 边", "当前内存中图谱的节点和边数量。"], - ["批次日志", "尚未合并到主快照的增量操作日志条目数。"], - ["运行版本", "运行时图谱的内部版本号,和版本号联动。"], - ]; - - const guideHtml = guidePairs.map(([term, desc]) => - `
${_escHtml(term)}${_escHtml(desc)}
` - ).join(""); + if (ps.hostProfile === "luker") { + diagnosticRows.splice(5, 0, + ["Sidecar 格式", sidecarFormatLabel], + ["Manifest rev", manifestRevisionLabel], + ["Journal", journalStateLabel], + ["Checkpoint rev", checkpointRevisionLabel], + ["缓存落后", cacheLagLabel], + ); + } el.innerHTML = `
-
持久化状态
- ${kvHtml} +
持久化状态
+
+ ${summaryPills.map((pill) => `${_escHtml(pill)}`).join("")} +
+
+ 这里只保留日常最常用的持久化信息。更偏技术性的字段已下沉到诊断细节,避免和右侧运行概览失衡。 +
+ ${renderRows(primaryRows)} +
+ + 查看诊断细节 + +
+ ${renderRows(diagnosticRows)} +
+
-
运行统计
- ${secondaryHtml} +
运行概览
+
+ 右侧专门展示当前图谱规模、处理进度和运行态前沿,减少左侧“持久化状态”承担太多运行职责。 +
+ ${renderRows(runtimeRows)}
-
-
字段说明
- ${guideHtml} -
`; }