From 3e90c643104f021bb3f7cf61ed4235e8d4be5557 Mon Sep 17 00:00:00 2001 From: Youzini-afk <13153778771cx@gmail.com> Date: Thu, 9 Apr 2026 23:11:51 +0800 Subject: [PATCH] Fix cognition owner label collisions in UI --- style.css | 55 ++++++++++++ tests/knowledge-state.mjs | 18 ++++ ui/panel.js | 181 +++++++++++++++++++++++++++++++++----- 3 files changed, 234 insertions(+), 20 deletions(-) diff --git a/style.css b/style.css index c03944d..dfd93e3 100644 --- a/style.css +++ b/style.css @@ -5275,6 +5275,13 @@ flex: 1; } +.bme-cog-owner-card__name-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} + .bme-cog-owner-card__name { font-size: 12px; font-weight: 700; @@ -5282,6 +5289,22 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + min-width: 0; + flex: 1; +} + +.bme-cog-owner-card__badge { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 1px 6px; + border-radius: 999px; + font-size: 9px; + font-weight: 700; + line-height: 1.4; + background: rgba(255, 255, 255, 0.08); + color: var(--bme-on-surface-dim); + flex-shrink: 0; } .bme-cog-owner-card__stats { @@ -5309,10 +5332,25 @@ gap: 12px; } +.bme-cog-detail-title-wrap { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} + .bme-cog-detail-name { font-size: 15px; font-weight: 700; color: var(--bme-on-surface); + word-break: break-word; +} + +.bme-cog-detail-meta { + font-size: 10px; + line-height: 1.5; + color: var(--bme-on-surface-dim); + word-break: break-word; } .bme-cog-detail-badge { @@ -5805,3 +5843,20 @@ grid-template-columns: 1fr; } } + +@media (max-width: 520px) { + .bme-cognition-line { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + .bme-cognition-line strong { + text-align: left; + } + + .bme-cog-detail-header { + align-items: flex-start; + flex-direction: column; + } +} diff --git a/tests/knowledge-state.mjs b/tests/knowledge-state.mjs index faffec0..f4cad2c 100644 --- a/tests/knowledge-state.mjs +++ b/tests/knowledge-state.mjs @@ -114,6 +114,18 @@ applyCognitionUpdates( ], { changedNodeIds: [bellEvent.id] }, ); +applyCognitionUpdates( + graph, + [ + { + ownerType: "user", + ownerName: "露西亚", + knownRefs: [bellEvent.id], + visibility: [{ ref: bellEvent.id, score: 0.8 }], + }, + ], + { changedNodeIds: [bellEvent.id] }, +); applyManualKnowledgeOverride(graph, { ownerKey: ownerA.ownerKey, nodeId: bellEvent.id, @@ -149,5 +161,11 @@ assert.ok( (entry) => entry.ownerName === "露西亚" && entry.knownCount >= 1, ), ); +const sameNameOwners = ownerList.filter((entry) => entry.ownerName === "露西亚"); +assert.equal(sameNameOwners.length, 2); +assert.deepEqual( + sameNameOwners.map((entry) => entry.ownerType).sort(), + ["character", "user"], +); console.log("knowledge-state tests passed"); diff --git a/ui/panel.js b/ui/panel.js index 4a48d62..fdb421a 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -984,6 +984,98 @@ function _ownerAvatarHsl(name) { return `hsl(${hue}, 55%, 42%)`; } +function _normalizeOwnerUiType(ownerType = "") { + const normalized = String(ownerType || "").trim(); + if (normalized === "user") return "user"; + if (normalized === "character") return "character"; + return ""; +} + +function _inferOwnerTypeFromKey(ownerKey = "") { + const normalizedOwnerKey = String(ownerKey || "").trim().toLowerCase(); + if (normalizedOwnerKey.startsWith("user:")) return "user"; + if (normalizedOwnerKey.startsWith("character:")) return "character"; + return ""; +} + +function _getOwnerTypeDisplayLabel(ownerType = "") { + const normalizedType = _normalizeOwnerUiType(ownerType); + if (normalizedType === "user") return "用户"; + if (normalizedType === "character") return "角色"; + return "Owner"; +} + +function _buildOwnerCollisionIndex(owners = []) { + const collisionIndex = new Map(); + for (const owner of Array.isArray(owners) ? owners : []) { + const baseName = + String(owner?.ownerName || owner?.ownerKey || "未命名角色").trim() || + "未命名角色"; + const nameKey = baseName.toLocaleLowerCase("zh-Hans-CN"); + const ownerType = _normalizeOwnerUiType(owner?.ownerType) || "unknown"; + const entry = collisionIndex.get(nameKey) || { + count: 0, + typeCounts: new Map(), + }; + entry.count += 1; + entry.typeCounts.set(ownerType, (entry.typeCounts.get(ownerType) || 0) + 1); + collisionIndex.set(nameKey, entry); + } + return collisionIndex; +} + +function _shortOwnerNodeId(owner = {}) { + const nodeId = String(owner?.nodeId || "").trim(); + if (!nodeId) return ""; + return nodeId.length > 6 ? nodeId.slice(0, 6) : nodeId; +} + +function _getOwnerDisplayInfo(owner = {}, collisionIndex = null) { + const baseName = + String(owner?.ownerName || owner?.ownerKey || "未命名角色").trim() || + "未命名角色"; + const ownerKey = String(owner?.ownerKey || "").trim(); + const ownerType = + _normalizeOwnerUiType(owner?.ownerType) || _inferOwnerTypeFromKey(ownerKey); + const typeLabel = _getOwnerTypeDisplayLabel(ownerType); + const collisionInfo = + collisionIndex instanceof Map + ? collisionIndex.get(baseName.toLocaleLowerCase("zh-Hans-CN")) || null + : null; + const typeCounts = + collisionInfo?.typeCounts instanceof Map ? collisionInfo.typeCounts : new Map(); + const totalCount = Number(collisionInfo?.count || 0); + const sameTypeCount = Number(typeCounts.get(ownerType || "unknown") || 0); + const hasCrossTypeCollision = totalCount > 1 && typeCounts.size > 1; + const shortNodeId = ownerType === "character" ? _shortOwnerNodeId(owner) : ""; + + let title = baseName; + if (hasCrossTypeCollision) { + title = `${baseName}(${typeLabel})`; + } else if (sameTypeCount > 1) { + title = + ownerType === "character" && shortNodeId + ? `${baseName}(${typeLabel} ${shortNodeId})` + : `${baseName}(${typeLabel})`; + } + + const subtitleParts = [typeLabel]; + if (ownerType === "character" && shortNodeId) { + subtitleParts.push(`#${shortNodeId}`); + } + + return { + title, + typeLabel, + subtitle: subtitleParts.join(" · "), + avatarText: baseName.charAt(0) || "?", + avatarSeed: ownerKey || `${ownerType}:${baseName}`, + tooltip: [title, ownerKey && ownerKey !== title ? ownerKey : ""] + .filter(Boolean) + .join(" · "), + }; +} + // ==================== 认知视图工作区 ==================== function _refreshCognitionWorkspace() { @@ -1016,6 +1108,7 @@ function _renderCogStatusStrip(graph, loadInfo, canRender) { const timelineState = graph?.timelineState || {}; const { owners, activeOwnerKey, activeOwner, activeOwnerLabels } = _getCurrentCognitionOwnerSummary(graph); + const collisionIndex = _buildOwnerCollisionIndex(owners); const activeRegion = String( historyState.activeRegion || historyState.lastExtractedRegion || regionState.manualActiveRegion || "", ).trim(); @@ -1046,7 +1139,9 @@ function _renderCogStatusStrip(graph, loadInfo, canRender) {