From 5c9a8a9df13cb36b1bb6097a9e3e2a4261dea85f Mon Sep 17 00:00:00 2001 From: Youzini-afk <13153778771cx@gmail.com> Date: Thu, 9 Apr 2026 23:38:14 +0800 Subject: [PATCH] Normalize mistaken user owners across extraction and cognition --- graph/knowledge-state.js | 128 ++++++++++++++++++++++++++- maintenance/extractor.js | 151 ++++++++++++++++++++++++++++++-- runtime/user-alias-utils.js | 137 +++++++++++++++++++++++++++++ tests/extractor-owner-scope.mjs | 74 ++++++++++++++++ tests/knowledge-state.mjs | 28 ++++++ ui/graph-renderer.js | 79 ++--------------- ui/panel.js | 13 +-- 7 files changed, 512 insertions(+), 98 deletions(-) create mode 100644 runtime/user-alias-utils.js diff --git a/graph/knowledge-state.js b/graph/knowledge-state.js index 9fb907a..e413052 100644 --- a/graph/knowledge-state.js +++ b/graph/knowledge-state.js @@ -3,6 +3,11 @@ import { getScopeRegionKey, normalizeMemoryScope, } from "./memory-scope.js"; +import { + aliasSetMatchesValue, + buildUserPovAliasNormalizedSet, + getHostUserAliasHints, +} from "../runtime/user-alias-utils.js"; export const KNOWLEDGE_STATE_VERSION = 1; export const REGION_STATE_VERSION = 1; @@ -65,6 +70,61 @@ function normalizeOwnerType(ownerType = "") { return ""; } +function appendAliasHintStrings(target, value) { + if (value == null) return; + if (typeof value === "string") { + const normalized = normalizeString(value); + if (normalized) target.push(normalized); + return; + } + if (Array.isArray(value)) { + for (const item of value) appendAliasHintStrings(target, item); + return; + } + if (typeof value === "object") { + appendAliasHintStrings(target, value.name1); + appendAliasHintStrings(target, value.userName); + appendAliasHintStrings(target, value.personaName); + appendAliasHintStrings(target, value.name); + appendAliasHintStrings(target, value.aliases); + } +} + +function buildUserAliasContext(graph = null, extraHints = []) { + const aliasHints = []; + appendAliasHintStrings(aliasHints, graph?.historyState?.activeUserPovOwner); + appendAliasHintStrings(aliasHints, getHostUserAliasHints()); + appendAliasHintStrings(aliasHints, extraHints); + const uniqueAliasHints = uniqueStrings(aliasHints); + return { + aliasHints: uniqueAliasHints, + aliasSet: buildUserPovAliasNormalizedSet(uniqueAliasHints), + preferredName: uniqueAliasHints[0] || "", + }; +} + +function shouldResolveCharacterOwnerAsUser( + graph, + ownerName = "", + nodeId = "", + userAliasContext = null, +) { + const normalizedOwnerName = normalizeString(ownerName); + if (!normalizedOwnerName) return false; + const aliasContext = userAliasContext || buildUserAliasContext(graph); + if (!aliasSetMatchesValue(aliasContext.aliasSet, normalizedOwnerName)) { + return false; + } + const normalizedNodeId = normalizeString(nodeId); + if (normalizedNodeId) { + const explicitNode = findCharacterNodeById(graph, normalizedNodeId); + if (explicitNode) { + return false; + } + } + return true; +} + function getCharacterNodes(graph) { return Array.isArray(graph?.nodes) ? graph.nodes.filter( @@ -241,7 +301,12 @@ function mergeKnowledgeOwnerEntries(baseEntry, incomingEntry) { return merged; } -function resolveCanonicalKnowledgeEntry(graph, ownerKey, entry) { +function resolveCanonicalKnowledgeEntry( + graph, + ownerKey, + entry, + userAliasContext = null, +) { const normalizedEntry = createDefaultKnowledgeOwnerState({ ...entry, ownerKey, @@ -252,6 +317,7 @@ function resolveCanonicalKnowledgeEntry(graph, ownerKey, entry) { ownerId: normalizedEntry.ownerName, nodeId: normalizedEntry.nodeId, aliases: normalizedEntry.aliases, + userAliasContext, }); return createDefaultKnowledgeOwnerState({ ...normalizedEntry, @@ -270,8 +336,14 @@ function resolveCanonicalKnowledgeEntry(graph, ownerKey, entry) { export function normalizeKnowledgeState(state = {}, graph = null) { const normalized = createDefaultKnowledgeState(state); const owners = {}; + const userAliasContext = buildUserAliasContext(graph); for (const [ownerKey, rawEntry] of Object.entries(normalized.owners || {})) { - const canonicalEntry = resolveCanonicalKnowledgeEntry(graph, ownerKey, rawEntry); + const canonicalEntry = resolveCanonicalKnowledgeEntry( + graph, + ownerKey, + rawEntry, + userAliasContext, + ); if (!canonicalEntry.ownerKey) continue; owners[canonicalEntry.ownerKey] = owners[canonicalEntry.ownerKey] ? mergeKnowledgeOwnerEntries(owners[canonicalEntry.ownerKey], canonicalEntry) @@ -321,16 +393,27 @@ export function resolveKnowledgeOwner(graph, input = {}) { }; } + const userAliasContext = + input?.userAliasContext && + input.userAliasContext.aliasSet instanceof Set + ? input.userAliasContext + : buildUserAliasContext(graph); + if (ownerType === OWNER_TYPE_USER) { - const ownerName = normalizeString( + const fallbackOwnerName = normalizeString( input.ownerName || input.ownerId || input.ownerKey, ); + const ownerName = userAliasContext.preferredName || fallbackOwnerName; return { ownerType, ownerKey: buildOwnerKey(ownerType, ownerName), ownerName, nodeId: "", - aliases: uniqueStrings(input.aliases || [ownerName]), + aliases: uniqueStrings([ + ...(Array.isArray(input.aliases) ? input.aliases : [input.aliases]), + ...userAliasContext.aliasHints, + ownerName, + ]), }; } @@ -342,6 +425,31 @@ export function resolveKnowledgeOwner(graph, input = {}) { ownerName = ownerName || normalizeString(explicitNode?.fields?.name); } + if ( + shouldResolveCharacterOwnerAsUser( + graph, + ownerName || input.ownerId, + nodeId, + userAliasContext, + ) + ) { + const userOwnerName = + userAliasContext.preferredName || + normalizeString(ownerName || input.ownerId || input.ownerKey); + return { + ownerType: OWNER_TYPE_USER, + ownerKey: buildOwnerKey(OWNER_TYPE_USER, userOwnerName), + ownerName: userOwnerName, + nodeId: "", + aliases: uniqueStrings([ + ...(Array.isArray(input.aliases) ? input.aliases : [input.aliases]), + ...userAliasContext.aliasHints, + ownerName, + userOwnerName, + ]), + }; + } + if (!nodeId && ownerName) { const matches = findCharacterNodeByName(graph, ownerName); if (matches.length === 1) { @@ -1224,6 +1332,7 @@ export function getKnowledgeOwnerEntry(graph, ownerKey = "") { export function listKnowledgeOwners(graph) { normalizeGraphCognitiveState(graph); const owners = new Map(); + const userAliasContext = buildUserAliasContext(graph); for (const entry of Object.values(graph.knowledgeState.owners || {})) { const normalizedEntry = createDefaultKnowledgeOwnerState(entry); @@ -1244,10 +1353,21 @@ export function listKnowledgeOwners(graph) { } for (const characterNode of getCharacterNodes(graph)) { + if ( + shouldResolveCharacterOwnerAsUser( + graph, + characterNode?.fields?.name, + "", + userAliasContext, + ) + ) { + continue; + } const resolvedOwner = resolveKnowledgeOwner(graph, { ownerType: OWNER_TYPE_CHARACTER, ownerName: characterNode?.fields?.name, nodeId: characterNode?.id, + userAliasContext, }); if (!resolvedOwner.ownerKey || owners.has(resolvedOwner.ownerKey)) continue; owners.set(resolvedOwner.ownerKey, { diff --git a/maintenance/extractor.js b/maintenance/extractor.js index c07fe91..40ad690 100644 --- a/maintenance/extractor.js +++ b/maintenance/extractor.js @@ -42,6 +42,11 @@ import { import { RELATION_TYPES } from "../graph/schema.js"; import { applyTaskRegex } from "../prompting/task-regex.js"; import { getSTContextForPrompt, getSTContextSnapshot } from "../host/st-context.js"; +import { + aliasSetMatchesValue, + buildUserPovAliasNormalizedSet, + getHostUserAliasHints, +} from "../runtime/user-alias-utils.js"; import { buildNodeVectorText, isDirectVectorConfig } from "../vector/vector-index.js"; function createAbortError(message = "操作已终止") { @@ -416,13 +421,70 @@ function normalizeExtractionOwnerText(value) { return String(value || "").trim(); } +function collectExtractorUserAliasHints(scopeRuntime = {}, extraHints = []) { + const hints = []; + const pushHint = (value) => { + const normalized = normalizeExtractionOwnerText(value); + if (!normalized || hints.includes(normalized)) return; + hints.push(normalized); + }; + const ingest = (value) => { + if (value == null) return; + if (typeof value === "string") { + pushHint(value); + return; + } + if (Array.isArray(value)) { + for (const item of value) ingest(item); + return; + } + if (typeof value === "object") { + pushHint(value.name1); + pushHint(value.userName); + pushHint(value.personaName); + pushHint(value.name); + ingest(value.aliases); + } + }; + + pushHint(scopeRuntime.activeUserOwner); + ingest(getHostUserAliasHints(extraHints)); + return hints; +} + +function buildExtractorUserAliasContext(scopeRuntime = {}, extraHints = []) { + const aliasHints = collectExtractorUserAliasHints(scopeRuntime, extraHints); + return { + aliasHints, + aliasSet: buildUserPovAliasNormalizedSet(aliasHints), + preferredName: aliasHints[0] || "", + }; +} + +function matchesExtractorUserAlias(ownerName = "", scopeRuntime = {}, extraHints = []) { + const aliasContext = buildExtractorUserAliasContext(scopeRuntime, extraHints); + return aliasSetMatchesValue(aliasContext.aliasSet, ownerName); +} + +function resolveExtractorUserOwnerName( + scopeRuntime = {}, + rawOwnerName = "", + extraHints = [], +) { + const aliasContext = buildExtractorUserAliasContext(scopeRuntime, [ + rawOwnerName, + ...(Array.isArray(extraHints) ? extraHints : [extraHints]), + ]); + return aliasContext.preferredName || normalizeExtractionOwnerText(rawOwnerName); +} + function resolveCharacterOwnerCandidate(graph, ownerName = "", ownerNodeId = "") { const resolved = resolveKnowledgeOwner(graph, { ownerType: "character", ownerName, nodeId: ownerNodeId, }); - return resolved?.ownerKey ? resolved : null; + return resolved?.ownerType === "character" && resolved?.ownerKey ? resolved : null; } function deriveExtractionOwnerContext( @@ -432,6 +494,13 @@ function deriveExtractionOwnerContext( ) { const ownerMap = new Map(); const registerCharacterOwner = (ownerName = "", ownerNodeId = "", source = "") => { + if ( + normalizeExtractionOwnerText(ownerName) && + !normalizeExtractionOwnerText(ownerNodeId) && + matchesExtractorUserAlias(ownerName, scopeRuntime) + ) { + return; + } const resolved = resolveCharacterOwnerCandidate(graph, ownerName, ownerNodeId); if (!resolved?.ownerKey) return; const existing = ownerMap.get(resolved.ownerKey) || { @@ -504,12 +573,42 @@ function normalizeCognitionUpdatesWithOwnerContext( for (const entry of Array.isArray(cognitionUpdates) ? cognitionUpdates : []) { const ownerType = normalizeExtractionOwnerText(entry?.ownerType); + const rawOwnerName = normalizeExtractionOwnerText( + entry?.ownerName || entry?.ownerId, + ); + const rawOwnerNodeId = normalizeExtractionOwnerText(entry?.ownerNodeId); if (ownerType === "character") { + if ( + rawOwnerName && + !rawOwnerNodeId && + matchesExtractorUserAlias(rawOwnerName, scopeRuntime) + ) { + const resolvedUserName = resolveExtractorUserOwnerName( + scopeRuntime, + rawOwnerName, + ); + if (!resolvedUserName) { + ownershipWarnings.push({ + kind: "invalid-owner-scope", + source: "cognitionUpdate", + ownerType: "user", + }); + continue; + } + normalized.push({ + ...entry, + ownerType: "user", + ownerName: resolvedUserName, + ownerId: resolvedUserName, + ownerNodeId: "", + }); + continue; + } const resolved = resolveCharacterOwnerCandidate( graph, - entry?.ownerName || entry?.ownerId, - entry?.ownerNodeId, + rawOwnerName, + rawOwnerNodeId, ) || ownerContext?.soleCharacterOwner || null; if (!resolved?.ownerKey) { ownershipWarnings.push({ @@ -530,9 +629,10 @@ function normalizeCognitionUpdatesWithOwnerContext( } if (ownerType === "user") { - const resolvedUserName = - normalizeExtractionOwnerText(entry?.ownerName || entry?.ownerId) || - normalizeExtractionOwnerText(scopeRuntime.activeUserOwner); + const resolvedUserName = resolveExtractorUserOwnerName( + scopeRuntime, + rawOwnerName, + ); if (!resolvedUserName) { ownershipWarnings.push({ kind: "invalid-owner-scope", @@ -1323,10 +1423,43 @@ function resolveOperationScope( const explicitOwnerName = normalizeExtractionOwnerText( rawScope?.ownerName || rawScope?.ownerId, ); + const explicitOwnerNodeId = normalizeExtractionOwnerText( + op?.scope?.ownerNodeId || op?.scope?.owner_node_id, + ); if (ownerType === "user") { - const userName = - explicitOwnerName || normalizeExtractionOwnerText(scopeRuntime.activeUserOwner); + const userName = resolveExtractorUserOwnerName( + scopeRuntime, + explicitOwnerName, + ); + if (!userName) { + return { + scope: fallbackScope, + invalidReason: "invalid-owner-scope", + }; + } + return { + scope: normalizeMemoryScope({ + ...(rawScope || {}), + layer: "pov", + ownerType: "user", + ownerId: userName, + ownerName: userName, + }), + invalidReason: "", + }; + } + + if ( + ownerType === "character" && + explicitOwnerName && + !explicitOwnerNodeId && + matchesExtractorUserAlias(explicitOwnerName, scopeRuntime) + ) { + const userName = resolveExtractorUserOwnerName( + scopeRuntime, + explicitOwnerName, + ); if (!userName) { return { scope: fallbackScope, @@ -1346,7 +1479,7 @@ function resolveOperationScope( } const resolvedCharacterOwner = - resolveCharacterOwnerCandidate(graph, explicitOwnerName, "") || + resolveCharacterOwnerCandidate(graph, explicitOwnerName, explicitOwnerNodeId) || ownerContext?.soleCharacterOwner || null; if (!resolvedCharacterOwner?.ownerKey) { diff --git a/runtime/user-alias-utils.js b/runtime/user-alias-utils.js new file mode 100644 index 0000000..054f88d --- /dev/null +++ b/runtime/user-alias-utils.js @@ -0,0 +1,137 @@ +function normalizeKeyForAlias(value) { + return String(value ?? "").trim().toLowerCase(); +} + +export function normalizeAliasMatchKey(value) { + let text = String(value ?? ""); + if (typeof text.normalize === "function") { + try { + text = text.normalize("NFKC"); + } catch { + // ignore invalid normalization environments + } + } + text = text.trim().toLowerCase(); + text = text.replace( + /[\s!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~\u00b7\u3000-\u303f\uff01-\uff0f\uff1a-\uff20\uff3b-\uff40\uff5b-\uff65\u2000-\u206f\u2e00-\u2e7f]+/g, + " ", + ); + return text.replace(/\s+/g, " ").trim(); +} + +export function collectAliasMatchVariants(raw) { + const variants = []; + const legacy = normalizeKeyForAlias(raw); + if (legacy) variants.push(legacy); + const soft = normalizeAliasMatchKey(raw); + if (soft) { + variants.push(soft); + const compact = soft.replace(/\s/g, ""); + if (compact && compact !== soft) { + variants.push(compact); + } + } + return [...new Set(variants.filter(Boolean))]; +} + +export function addAliasMatchVariantsToSet(target, raw) { + if (!(target instanceof Set)) return target; + for (const variant of collectAliasMatchVariants(raw)) { + target.add(variant); + } + return target; +} + +function ingestAliasHints(target, hints) { + if (hints == null) return; + if (typeof hints === "string") { + addAliasMatchVariantsToSet(target, hints); + return; + } + if (Array.isArray(hints)) { + for (const item of hints) { + ingestAliasHints(target, item); + } + return; + } + if (typeof hints === "object") { + addAliasMatchVariantsToSet(target, hints.name1); + addAliasMatchVariantsToSet(target, hints.userName); + addAliasMatchVariantsToSet(target, hints.personaName); + addAliasMatchVariantsToSet(target, hints.name); + if (Array.isArray(hints.aliases)) { + for (const alias of hints.aliases) { + addAliasMatchVariantsToSet(target, alias); + } + } + } +} + +export function buildUserPovAliasNormalizedSet(hints) { + const aliasSet = new Set(); + ingestAliasHints(aliasSet, hints); + return aliasSet; +} + +export function aliasSetMatchesValue(aliasSet, value) { + if (!(aliasSet instanceof Set) || aliasSet.size === 0) return false; + for (const variant of collectAliasMatchVariants(value)) { + if (aliasSet.has(variant)) { + return true; + } + } + return false; +} + +function safeReadHostContext() { + const candidates = [ + globalThis.SillyTavern?.getContext?.(), + globalThis.getContext?.(), + globalThis.__stBmeTestContext, + ]; + for (const candidate of candidates) { + if (candidate && typeof candidate === "object") { + return candidate; + } + } + return null; +} + +function pushAliasHint(target, value) { + const normalized = String(value ?? "").trim(); + if (!normalized || target.includes(normalized)) return; + target.push(normalized); +} + +export function getHostUserAliasHints(extraHints = null) { + const hints = []; + const context = safeReadHostContext(); + pushAliasHint(hints, context?.name1); + pushAliasHint(hints, context?.user?.name); + pushAliasHint(hints, context?.userName); + pushAliasHint(hints, context?.prompt?.userName); + + const ingest = (value) => { + if (value == null) return; + if (typeof value === "string") { + pushAliasHint(hints, value); + return; + } + if (Array.isArray(value)) { + for (const item of value) ingest(item); + return; + } + if (typeof value === "object") { + pushAliasHint(hints, value.name1); + pushAliasHint(hints, value.userName); + pushAliasHint(hints, value.personaName); + pushAliasHint(hints, value.name); + if (Array.isArray(value.aliases)) { + for (const alias of value.aliases) ingest(alias); + } + } + }; + + ingest(extraHints); + return hints; +} diff --git a/tests/extractor-owner-scope.mjs b/tests/extractor-owner-scope.mjs index 1ed7648..1bb0040 100644 --- a/tests/extractor-owner-scope.mjs +++ b/tests/extractor-owner-scope.mjs @@ -209,4 +209,78 @@ globalThis.__stBmeTestContext = { } } +{ + const graph = createEmptyGraph(); + globalThis.__stBmeTestContext.name1 = "邱谁"; + globalThis.__stBmeTestContext.name2 = "群像卡"; + const restore = setTestOverrides({ + llm: { + async callLLMForJSON() { + return { + operations: [ + { + action: "create", + type: "pov_memory", + scope: { + layer: "pov", + ownerType: "character", + ownerName: "邱 谁", + ownerId: "邱 谁", + }, + fields: { summary: "她感觉对方在试探自己的底线" }, + }, + ], + cognitionUpdates: [ + { + ownerType: "character", + ownerName: "【邱谁】", + knownRefs: [], + }, + ], + regionUpdates: {}, + }; + }, + }, + }); + + try { + const result = await extractMemories({ + graph, + messages: [{ seq: 7, role: "assistant", content: "用户名误标测试" }], + startSeq: 7, + endSeq: 7, + schema: DEFAULT_NODE_SCHEMA, + embeddingConfig: null, + settings: {}, + }); + + assert.equal(result.success, true); + const povNode = graph.nodes.find( + (node) => !node.archived && node.type === "pov_memory", + ); + assert.ok(povNode); + assert.equal(povNode.scope?.ownerType, "user"); + assert.equal(povNode.scope?.ownerName, "邱谁"); + const knowledgeOwners = Object.values(graph.knowledgeState?.owners || {}); + assert.equal( + knowledgeOwners.some( + (entry) => + String(entry?.ownerType || "") === "character" && + String(entry?.ownerName || "") === "邱谁", + ), + false, + ); + assert.equal( + knowledgeOwners.some( + (entry) => + String(entry?.ownerType || "") === "user" && + String(entry?.ownerName || "") === "邱谁", + ), + true, + ); + } finally { + restore(); + } +} + console.log("extractor-owner-scope tests passed"); diff --git a/tests/knowledge-state.mjs b/tests/knowledge-state.mjs index f4cad2c..d7c0ce6 100644 --- a/tests/knowledge-state.mjs +++ b/tests/knowledge-state.mjs @@ -14,6 +14,14 @@ import { setManualActiveRegion, } from "../graph/knowledge-state.js"; +globalThis.SillyTavern = { + getContext() { + return { + name1: "露西亚", + }; + }, +}; + const graph = createEmptyGraph(); const erinA = createNode({ type: "character", @@ -168,4 +176,24 @@ assert.deepEqual( ["character", "user"], ); +const aliasMatchedUserOwner = resolveKnowledgeOwner(graph, { + ownerType: "character", + ownerName: "露 西 亚", +}); +assert.equal(aliasMatchedUserOwner.ownerType, "user"); +assert.equal(aliasMatchedUserOwner.ownerName, "露西亚"); + +const syntheticGraph = createEmptyGraph(); +syntheticGraph.historyState.activeUserPovOwner = "玩家"; +addNode( + syntheticGraph, + createNode({ + type: "character", + fields: { name: "玩 家" }, + seq: 1, + }), +); +const syntheticOwners = listKnowledgeOwners(syntheticGraph); +assert.equal(syntheticOwners.some((entry) => entry.ownerType === "character"), false); + console.log("knowledge-state tests passed"); diff --git a/ui/graph-renderer.js b/ui/graph-renderer.js index ef381a3..824706a 100644 --- a/ui/graph-renderer.js +++ b/ui/graph-renderer.js @@ -4,6 +4,10 @@ import { getNodeColors } from './themes.js'; import { getGraphNodeLabel, getNodeDisplayName } from '../graph/node-labels.js'; import { normalizeMemoryScope } from '../graph/memory-scope.js'; +import { + aliasSetMatchesValue, + buildUserPovAliasNormalizedSet, +} from '../runtime/user-alias-utils.js'; /** * @typedef {Object} GraphNode @@ -95,83 +99,10 @@ function normalizeKeyForPartition(value) { return String(value ?? '').trim().toLowerCase(); } -/** - * 宿主别名与 POV owner 比对:忽略大小写、多空格、常见中英文标点/符号差(NFKC)。 - * 不用于 charMap 主键,仅用于「是否同一用户」的宽松匹配。 - */ -function normalizeAliasMatchKey(value) { - let s = String(value ?? ''); - if (typeof s.normalize === 'function') { - try { - s = s.normalize('NFKC'); - } catch { - /* ignore */ - } - } - s = s.trim().toLowerCase(); - // 标点、间隔号、各类空白等统一成空格,再压成单空格 - s = s.replace( - /[\s!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~\u00b7\u3000-\u303f\uff01-\uff0f\uff1a-\uff20\uff3b-\uff40\uff5b-\uff65\u2000-\u206f\u2e00-\u2e7f]+/g, - ' ', - ); - s = s.replace(/\s+/g, ' ').trim(); - return s; -} - -/** 同一名称的多种可比形式(兼容老数据只做了 trim+lower) */ -function collectAliasMatchVariants(raw) { - const variants = []; - const leg = normalizeKeyForPartition(raw); - if (leg) variants.push(leg); - const soft = normalizeAliasMatchKey(raw); - if (soft) { - variants.push(soft); - const compact = soft.replace(/\s/g, ''); - if (compact && compact !== soft) variants.push(compact); - } - return variants; -} - -function addAliasMatchVariantsToSet(set, raw) { - for (const k of collectAliasMatchVariants(raw)) { - if (k) set.add(k); - } -} - -/** - * 将宿主侧「用户显示名」候选归一为分区用 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(); - if (hints == null) return set; - const ingest = (v) => addAliasMatchVariantsToSet(set, v); - if (typeof hints === 'string') { - ingest(hints); - return set; - } - if (Array.isArray(hints)) { - for (const item of hints) ingest(item); - return set; - } - if (typeof hints === 'object') { - ingest(hints.name1); - ingest(hints.userName); - ingest(hints.personaName); - if (Array.isArray(hints.aliases)) { - for (const a of hints.aliases) ingest(a); - } - } - return set; -} - function scopeMatchesHostUserAliases(scope, aliasSet) { if (!(aliasSet instanceof Set) || aliasSet.size === 0) return false; for (const field of [scope.ownerName, scope.ownerId]) { - for (const k of collectAliasMatchVariants(field)) { - if (k && aliasSet.has(k)) return true; - } + if (aliasSetMatchesValue(aliasSet, field)) return true; } return false; } diff --git a/ui/panel.js b/ui/panel.js index fdb421a..1f1d5ac 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -1,6 +1,5 @@ // ST-BME: 操控面板交互逻辑 -import { getContext } from "../../../../extensions.js"; import { GraphRenderer } from "./graph-renderer.js"; import { getNodeDisplayName } from "../graph/node-labels.js"; import { @@ -9,6 +8,7 @@ import { normalizeMemoryScope, } from "../graph/memory-scope.js"; import { listKnowledgeOwners } from "../graph/knowledge-state.js"; +import { getHostUserAliasHints } from "../runtime/user-alias-utils.js"; import { describeNodeStoryTime, describeStoryTime, @@ -2862,16 +2862,7 @@ function _getInjectionSectionFlavor(title = "") { /** 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 []; - } + return getHostUserAliasHints(); } function _refreshGraph() {