diff --git a/graph-renderer.js b/graph-renderer.js index 6754398..80ba2ae 100644 --- a/graph-renderer.js +++ b/graph-renderer.js @@ -95,6 +95,46 @@ function normalizeKeyForPartition(value) { return String(value ?? '').trim().toLowerCase(); } +/** + * 将宿主侧「用户显示名」候选归一为分区用 Set,用于把误标为 character 的用户 POV 拉回用户区。 + * @param {string|string[]|{name1?:string,userName?:string,personaName?:string,aliases?:string[]}|null|undefined} hints + * @returns {Set} + */ +export function buildUserPovAliasNormalizedSet(hints) { + const set = new Set(); + const add = (v) => { + const k = normalizeKeyForPartition(v); + if (k) set.add(k); + }; + if (hints == null) return set; + if (typeof hints === 'string') { + add(hints); + return set; + } + if (Array.isArray(hints)) { + for (const item of hints) add(item); + return set; + } + if (typeof hints === 'object') { + add(hints.name1); + add(hints.userName); + add(hints.personaName); + if (Array.isArray(hints.aliases)) { + for (const a of hints.aliases) add(a); + } + } + return set; +} + +function scopeMatchesHostUserAliases(scope, aliasSet) { + if (!(aliasSet instanceof Set) || aliasSet.size === 0) return false; + const nameKey = normalizeKeyForPartition(scope.ownerName); + const idKey = normalizeKeyForPartition(scope.ownerId); + if (nameKey && aliasSet.has(nameKey)) return true; + if (idKey && aliasSet.has(idKey)) return true; + return false; +} + function characterPovLabelFromNodes(arr) { if (!arr?.length) return '·'; for (const n of arr) { @@ -108,10 +148,12 @@ function characterPovLabelFromNodes(arr) { return '·'; } -function partitionNodesByScope(nodes) { +function partitionNodesByScope(nodes, userPovAliasSet = null) { const objective = []; const userPov = []; const charMap = new Map(); + const aliasSet = + userPovAliasSet instanceof Set ? userPovAliasSet : new Set(); for (const node of nodes) { const scope = normalizeMemoryScope(node.raw?.scope); @@ -120,6 +162,12 @@ function partitionNodesByScope(nodes) { node.regionKey = 'objective'; continue; } + // 优先:宿主用户显示名与 ownerName/ownerId 一致时一律归用户 POV(修正提取阶段误标 character) + if (scopeMatchesHostUserAliases(scope, aliasSet)) { + userPov.push(node); + node.regionKey = 'user'; + continue; + } if (scope.ownerType === 'user') { userPov.push(node); node.regionKey = 'user'; @@ -166,6 +214,9 @@ export class GraphRenderer { this.colors = getNodeColors(themeName); this.themeName = themeName; this.config = { ...DEFAULT_LAYOUT_CONFIG, ...fromForce, ...layoutOverride }; + this._userPovAliasSet = buildUserPovAliasNormalizedSet( + isLegacy ? null : options?.userPovAliases, + ); this._regionPanels = []; this._lastGraph = null; @@ -200,10 +251,19 @@ export class GraphRenderer { * 加载图谱数据 * @param {object} graph - 完整的 graph state */ - loadGraph(graph) { + /** + * @param {object} graph + * @param {{ userPovAliases?: string|string[]|object }} [layoutHints] + */ + loadGraph(graph, layoutHints = {}) { const prevSelectedId = this.selectedNode?.id || null; this.nodeMap.clear(); this._lastGraph = graph; + if (layoutHints && Object.prototype.hasOwnProperty.call(layoutHints, 'userPovAliases')) { + this._userPovAliasSet = buildUserPovAliasNormalizedSet( + layoutHints.userPovAliases, + ); + } const dpr = window.devicePixelRatio || 1; const W = this.canvas.width / dpr; @@ -239,7 +299,7 @@ export class GraphRenderer { relation: e.relation || 'related', })); - const parts = partitionNodesByScope(this.nodes); + const parts = partitionNodesByScope(this.nodes, this._userPovAliasSet); this._regionPanels = this._computeRegionPanels(W, H, parts); this._layoutAllPartitions(parts); this._simulateNeuralWithinRegions(this.config.neuralIterations); diff --git a/panel.js b/panel.js index b3f897c..65132a1 100644 --- a/panel.js +++ b/panel.js @@ -1,6 +1,7 @@ // ST-BME: 操控面板交互逻辑 import { callGenericPopup, POPUP_TYPE } from "../../../popup.js"; +import { getContext } from "../../../extensions.js"; import { renderTemplateAsync } from "../../../templates.js"; import { GraphRenderer } from "./graph-renderer.js"; import { getNodeDisplayName } from "./node-labels.js"; @@ -672,15 +673,19 @@ export function openPanel() { const settings = _getSettings?.() || {}; const themeName = settings.panelTheme || "crimson"; + const graphOpts = { + theme: themeName, + userPovAliases: _hostUserPovAliasHintsForGraph(), + }; const canvas = document.getElementById("bme-graph-canvas"); if (canvas && !graphRenderer && !isMobile) { - graphRenderer = new GraphRenderer(canvas, themeName); + graphRenderer = new GraphRenderer(canvas, graphOpts); graphRenderer.onNodeSelect = (node) => _showNodeDetail(node); } const mobileCanvas = document.getElementById("bme-mobile-graph-canvas"); if (mobileCanvas && !mobileGraphRenderer && isMobile) { - mobileGraphRenderer = new GraphRenderer(mobileCanvas, themeName); + mobileGraphRenderer = new GraphRenderer(mobileCanvas, graphOpts); mobileGraphRenderer.onNodeSelect = (node) => _showNodeDetail(node); } @@ -1203,11 +1208,26 @@ async function _refreshInjectionPreview() { // ==================== 图谱 ==================== +/** SillyTavern 用户显示名(name1),用于图谱分区:误标为角色的用户 POV 强制归用户区 */ +function _hostUserPovAliasHintsForGraph() { + try { + const ctx = typeof getContext === "function" ? getContext() : null; + const out = []; + if (ctx?.name1 && String(ctx.name1).trim()) { + out.push(String(ctx.name1).trim()); + } + return out; + } catch { + return []; + } +} + function _refreshGraph() { const graph = _getGraph?.(); if (!graph) return; - graphRenderer?.loadGraph(graph); - mobileGraphRenderer?.loadGraph(graph); + const hints = { userPovAliases: _hostUserPovAliasHintsForGraph() }; + graphRenderer?.loadGraph(graph, hints); + mobileGraphRenderer?.loadGraph(graph, hints); } function _buildLegend() { diff --git a/recall-message-ui.js b/recall-message-ui.js index 7a37b97..78b8d91 100644 --- a/recall-message-ui.js +++ b/recall-message-ui.js @@ -1,8 +1,22 @@ // ST-BME: 消息级召回卡片 UI // 纯 DOM 构建模块,不含模块级 mutable state +import { getContext } from "../../../extensions.js"; import { GraphRenderer } from "./graph-renderer.js"; +function _hostUserPovAliasHintsForRecallCanvas() { + try { + const ctx = typeof getContext === "function" ? getContext() : null; + const out = []; + if (ctx?.name1 && String(ctx.name1).trim()) { + out.push(String(ctx.name1).trim()); + } + return out; + } catch { + return []; + } +} + // ==================== 常量 ==================== export const RECALL_CARD_FORCE_CONFIG = { @@ -297,6 +311,7 @@ export function createRecallCardElement({ renderer = new GraphRenderer(canvas, { theme: themeName, forceConfig: RECALL_CARD_FORCE_CONFIG, + userPovAliases: _hostUserPovAliasHintsForRecallCanvas(), onNodeClick: (node) => { if (typeof activeCallbacks.onNodeClick === "function") { activeCallbacks.onNodeClick(messageIndex, node); @@ -308,7 +323,9 @@ export function createRecallCardElement({ } }, }); - renderer.loadGraph(resolvedSubGraph); + renderer.loadGraph(resolvedSubGraph, { + userPovAliases: _hostUserPovAliasHintsForRecallCanvas(), + }); } // 元信息行