mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
Add cognition owner management flows
This commit is contained in:
@@ -1526,6 +1526,452 @@ export function updateRegionAdjacencyManual(graph, region = "", adjacent = []) {
|
||||
return { ok: true, region: normalizedRegion };
|
||||
}
|
||||
|
||||
function buildKnowledgeOwnerAliasVariantSet(owner = {}) {
|
||||
return buildOwnerAliasVariantSet([
|
||||
owner?.ownerName,
|
||||
...(Array.isArray(owner?.aliases) ? owner.aliases : []),
|
||||
]);
|
||||
}
|
||||
|
||||
function doesKnowledgeOwnerVariantSetMatchValue(variantSet, value = "") {
|
||||
if (!(variantSet instanceof Set) || variantSet.size === 0) return false;
|
||||
for (const variant of collectAliasMatchVariants(value)) {
|
||||
if (variantSet.has(variant)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function doesScopeReferenceCharacterOwner(graph, scope = {}, owner = {}) {
|
||||
const normalizedScope = normalizeMemoryScope(scope);
|
||||
if (normalizeOwnerType(normalizedScope.ownerType) !== OWNER_TYPE_CHARACTER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedOwnerKey = normalizeString(owner?.ownerKey);
|
||||
if (normalizedOwnerKey) {
|
||||
const resolvedOwnerKey = resolveKnowledgeOwner(graph, {
|
||||
ownerType: OWNER_TYPE_CHARACTER,
|
||||
ownerName: normalizedScope.ownerName || normalizedScope.ownerId,
|
||||
ownerId: normalizedScope.ownerId,
|
||||
}).ownerKey;
|
||||
if (resolvedOwnerKey && resolvedOwnerKey === normalizedOwnerKey) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const aliasVariantSet = buildKnowledgeOwnerAliasVariantSet(owner);
|
||||
return (
|
||||
doesKnowledgeOwnerVariantSetMatchValue(aliasVariantSet, normalizedScope.ownerName) ||
|
||||
doesKnowledgeOwnerVariantSetMatchValue(aliasVariantSet, normalizedScope.ownerId)
|
||||
);
|
||||
}
|
||||
|
||||
function doesCharacterNodeReferenceOwner(graph, node = {}, owner = {}) {
|
||||
if (
|
||||
!node ||
|
||||
node.archived === true ||
|
||||
normalizeString(node?.type) !== "character" ||
|
||||
!normalizeString(node?.fields?.name)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedOwnerKey = normalizeString(owner?.ownerKey);
|
||||
if (normalizedOwnerKey) {
|
||||
const resolvedOwnerKey = resolveKnowledgeOwner(graph, {
|
||||
ownerType: OWNER_TYPE_CHARACTER,
|
||||
ownerName: node?.fields?.name,
|
||||
nodeId: node?.id,
|
||||
}).ownerKey;
|
||||
if (resolvedOwnerKey && resolvedOwnerKey === normalizedOwnerKey) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const aliasVariantSet = buildKnowledgeOwnerAliasVariantSet(owner);
|
||||
return (
|
||||
doesKnowledgeOwnerVariantSetMatchValue(aliasVariantSet, node?.fields?.name) ||
|
||||
normalizeString(owner?.nodeId) === normalizeString(node?.id)
|
||||
);
|
||||
}
|
||||
|
||||
function collectCharacterNodesForOwner(graph, owner = {}) {
|
||||
return Array.isArray(graph?.nodes)
|
||||
? graph.nodes.filter((node) => doesCharacterNodeReferenceOwner(graph, node, owner))
|
||||
: [];
|
||||
}
|
||||
|
||||
function rewriteScopedCharacterOwnerReferences(
|
||||
graph,
|
||||
owner = {},
|
||||
{ ownerName = "", ownerId = "" } = {},
|
||||
) {
|
||||
const normalizedOwnerName = normalizeString(ownerName || ownerId);
|
||||
const normalizedOwnerId = normalizeString(ownerId || ownerName);
|
||||
if (!normalizedOwnerName || !normalizedOwnerId) {
|
||||
return {
|
||||
changedNodeIds: [],
|
||||
changedEdgeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const changedNodeIds = [];
|
||||
for (const node of Array.isArray(graph?.nodes) ? graph.nodes : []) {
|
||||
const normalizedScope = normalizeMemoryScope(node?.scope);
|
||||
if (!doesScopeReferenceCharacterOwner(graph, normalizedScope, owner)) {
|
||||
continue;
|
||||
}
|
||||
node.scope = normalizeMemoryScope({
|
||||
...normalizedScope,
|
||||
layer: "pov",
|
||||
ownerType: OWNER_TYPE_CHARACTER,
|
||||
ownerName: normalizedOwnerName,
|
||||
ownerId: normalizedOwnerId,
|
||||
});
|
||||
const normalizedNodeId = normalizeString(node?.id);
|
||||
if (normalizedNodeId) changedNodeIds.push(normalizedNodeId);
|
||||
}
|
||||
|
||||
let changedEdgeCount = 0;
|
||||
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
||||
const normalizedScope = normalizeMemoryScope(edge?.scope);
|
||||
if (!doesScopeReferenceCharacterOwner(graph, normalizedScope, owner)) {
|
||||
continue;
|
||||
}
|
||||
edge.scope = normalizeMemoryScope({
|
||||
...normalizedScope,
|
||||
layer: "pov",
|
||||
ownerType: OWNER_TYPE_CHARACTER,
|
||||
ownerName: normalizedOwnerName,
|
||||
ownerId: normalizedOwnerId,
|
||||
});
|
||||
changedEdgeCount += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
changedNodeIds: uniqueIds(changedNodeIds),
|
||||
changedEdgeCount,
|
||||
};
|
||||
}
|
||||
|
||||
function renameCharacterNodesForOwner(graph, owner = {}, nextName = "") {
|
||||
const normalizedNextName = normalizeString(nextName);
|
||||
if (!normalizedNextName) return [];
|
||||
|
||||
const changedNodeIds = [];
|
||||
for (const node of collectCharacterNodesForOwner(graph, owner)) {
|
||||
node.fields = {
|
||||
...(node?.fields || {}),
|
||||
name: normalizedNextName,
|
||||
};
|
||||
const normalizedNodeId = normalizeString(node?.id);
|
||||
if (normalizedNodeId) changedNodeIds.push(normalizedNodeId);
|
||||
}
|
||||
return uniqueIds(changedNodeIds);
|
||||
}
|
||||
|
||||
function updateHistoryOwnerReferences(historyState, previousOwner = {}, nextOwner = null) {
|
||||
if (!historyState || typeof historyState !== "object") return;
|
||||
|
||||
const previousOwnerKey = normalizeString(previousOwner?.ownerKey);
|
||||
const nextOwnerKey = normalizeString(nextOwner?.ownerKey);
|
||||
const nextOwnerName = normalizeString(nextOwner?.ownerName);
|
||||
const previousAliasVariants = buildKnowledgeOwnerAliasVariantSet(previousOwner);
|
||||
|
||||
if (
|
||||
doesKnowledgeOwnerVariantSetMatchValue(
|
||||
previousAliasVariants,
|
||||
historyState.activeCharacterPovOwner,
|
||||
)
|
||||
) {
|
||||
historyState.activeCharacterPovOwner = nextOwnerName;
|
||||
}
|
||||
|
||||
if (normalizeString(historyState.activeRecallOwnerKey) === previousOwnerKey) {
|
||||
historyState.activeRecallOwnerKey = nextOwnerKey;
|
||||
}
|
||||
|
||||
historyState.recentRecallOwnerKeys = uniqueStrings(
|
||||
(historyState.recentRecallOwnerKeys || [])
|
||||
.map((value) => {
|
||||
const normalizedValue = normalizeString(value);
|
||||
if (!normalizedValue) return "";
|
||||
return normalizedValue === previousOwnerKey ? nextOwnerKey : normalizedValue;
|
||||
})
|
||||
.filter(Boolean),
|
||||
).slice(0, RECENT_RECALL_OWNER_LIMIT);
|
||||
}
|
||||
|
||||
function archiveOwnerCharacterNodesForMerge(graph, sourceOwner = {}, targetOwner = {}) {
|
||||
const sourceNodes = collectCharacterNodesForOwner(graph, sourceOwner);
|
||||
const archivedNodeIds = [];
|
||||
let adoptedNodeId = normalizeString(targetOwner?.nodeId);
|
||||
|
||||
if (!adoptedNodeId && sourceNodes.length > 0) {
|
||||
const keeper = sourceNodes[0];
|
||||
keeper.fields = {
|
||||
...(keeper?.fields || {}),
|
||||
name: normalizeString(targetOwner?.ownerName || keeper?.fields?.name),
|
||||
};
|
||||
keeper.archived = false;
|
||||
adoptedNodeId = normalizeString(keeper?.id);
|
||||
|
||||
for (const node of sourceNodes.slice(1)) {
|
||||
node.archived = true;
|
||||
const normalizedNodeId = normalizeString(node?.id);
|
||||
if (normalizedNodeId) archivedNodeIds.push(normalizedNodeId);
|
||||
}
|
||||
return {
|
||||
adoptedNodeId,
|
||||
archivedNodeIds: uniqueIds(archivedNodeIds),
|
||||
};
|
||||
}
|
||||
|
||||
for (const node of sourceNodes) {
|
||||
node.archived = true;
|
||||
const normalizedNodeId = normalizeString(node?.id);
|
||||
if (normalizedNodeId) archivedNodeIds.push(normalizedNodeId);
|
||||
}
|
||||
return {
|
||||
adoptedNodeId,
|
||||
archivedNodeIds: uniqueIds(archivedNodeIds),
|
||||
};
|
||||
}
|
||||
|
||||
function archiveCharacterNodesForOwner(graph, owner = {}) {
|
||||
const archivedNodeIds = [];
|
||||
for (const node of collectCharacterNodesForOwner(graph, owner)) {
|
||||
node.archived = true;
|
||||
const normalizedNodeId = normalizeString(node?.id);
|
||||
if (normalizedNodeId) archivedNodeIds.push(normalizedNodeId);
|
||||
}
|
||||
return uniqueIds(archivedNodeIds);
|
||||
}
|
||||
|
||||
function archivePovNodesForOwner(graph, owner = {}) {
|
||||
const archivedNodeIds = [];
|
||||
for (const node of Array.isArray(graph?.nodes) ? graph.nodes : []) {
|
||||
const normalizedScope = normalizeMemoryScope(node?.scope);
|
||||
if (normalizedScope.layer !== "pov") continue;
|
||||
if (!doesScopeReferenceCharacterOwner(graph, normalizedScope, owner)) continue;
|
||||
node.archived = true;
|
||||
const normalizedNodeId = normalizeString(node?.id);
|
||||
if (normalizedNodeId) archivedNodeIds.push(normalizedNodeId);
|
||||
}
|
||||
return uniqueIds(archivedNodeIds);
|
||||
}
|
||||
|
||||
export function renameKnowledgeOwner(graph, ownerKey = "", nextName = "") {
|
||||
normalizeGraphCognitiveState(graph);
|
||||
const normalizedOwnerKey = normalizeString(ownerKey);
|
||||
const normalizedNextName = normalizeString(nextName);
|
||||
if (!normalizedOwnerKey || !normalizedNextName) {
|
||||
return { ok: false, reason: "missing-owner-or-name" };
|
||||
}
|
||||
|
||||
const sourceEntry = createDefaultKnowledgeOwnerState(
|
||||
graph?.knowledgeState?.owners?.[normalizedOwnerKey] || {},
|
||||
);
|
||||
if (!sourceEntry.ownerKey) {
|
||||
return { ok: false, reason: "owner-not-found" };
|
||||
}
|
||||
if (normalizeOwnerType(sourceEntry.ownerType) !== OWNER_TYPE_CHARACTER) {
|
||||
return { ok: false, reason: "unsupported-owner-type" };
|
||||
}
|
||||
if (normalizeKey(sourceEntry.ownerName) === normalizeKey(normalizedNextName)) {
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: normalizedOwnerKey,
|
||||
unchanged: true,
|
||||
};
|
||||
}
|
||||
|
||||
const scopeRewrite = rewriteScopedCharacterOwnerReferences(graph, sourceEntry, {
|
||||
ownerName: normalizedNextName,
|
||||
ownerId: normalizedNextName,
|
||||
});
|
||||
const renamedCharacterNodeIds = renameCharacterNodesForOwner(
|
||||
graph,
|
||||
sourceEntry,
|
||||
normalizedNextName,
|
||||
);
|
||||
const nextOwnerKey = buildOwnerKey(
|
||||
OWNER_TYPE_CHARACTER,
|
||||
normalizedNextName,
|
||||
sourceEntry.nodeId,
|
||||
graph,
|
||||
);
|
||||
const nextEntry = createDefaultKnowledgeOwnerState({
|
||||
...sourceEntry,
|
||||
ownerKey: nextOwnerKey,
|
||||
ownerName: normalizedNextName,
|
||||
aliases: uniqueStrings([
|
||||
normalizedNextName,
|
||||
sourceEntry.ownerName,
|
||||
...(sourceEntry.aliases || []),
|
||||
]),
|
||||
updatedAt: Date.now(),
|
||||
lastSource: "manual-owner-rename",
|
||||
});
|
||||
|
||||
delete graph.knowledgeState.owners[normalizedOwnerKey];
|
||||
graph.knowledgeState.owners[nextOwnerKey] = graph.knowledgeState.owners[nextOwnerKey]
|
||||
? mergeKnowledgeOwnerEntries(graph.knowledgeState.owners[nextOwnerKey], nextEntry)
|
||||
: nextEntry;
|
||||
|
||||
const resolvedNextOwner = createDefaultKnowledgeOwnerState(
|
||||
graph.knowledgeState.owners[nextOwnerKey],
|
||||
);
|
||||
updateHistoryOwnerReferences(graph.historyState, sourceEntry, resolvedNextOwner);
|
||||
graph.knowledgeState = normalizeKnowledgeState(graph.knowledgeState, graph);
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: nextOwnerKey,
|
||||
previousOwnerKey: normalizedOwnerKey,
|
||||
renamedCharacterNodeIds,
|
||||
updatedPovNodeIds: scopeRewrite.changedNodeIds,
|
||||
updatedPovEdgeCount: scopeRewrite.changedEdgeCount,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeKnowledgeOwners(
|
||||
graph,
|
||||
{ sourceOwnerKey = "", targetOwnerKey = "" } = {},
|
||||
) {
|
||||
normalizeGraphCognitiveState(graph);
|
||||
const normalizedSourceOwnerKey = normalizeString(sourceOwnerKey);
|
||||
const normalizedTargetOwnerKey = normalizeString(targetOwnerKey);
|
||||
if (!normalizedSourceOwnerKey || !normalizedTargetOwnerKey) {
|
||||
return { ok: false, reason: "missing-owner-key" };
|
||||
}
|
||||
if (normalizedSourceOwnerKey === normalizedTargetOwnerKey) {
|
||||
return { ok: false, reason: "same-owner" };
|
||||
}
|
||||
|
||||
const sourceEntry = createDefaultKnowledgeOwnerState(
|
||||
graph?.knowledgeState?.owners?.[normalizedSourceOwnerKey] || {},
|
||||
);
|
||||
const targetEntry = createDefaultKnowledgeOwnerState(
|
||||
graph?.knowledgeState?.owners?.[normalizedTargetOwnerKey] || {},
|
||||
);
|
||||
if (!sourceEntry.ownerKey || !targetEntry.ownerKey) {
|
||||
return { ok: false, reason: "owner-not-found" };
|
||||
}
|
||||
if (
|
||||
normalizeOwnerType(sourceEntry.ownerType) !== OWNER_TYPE_CHARACTER ||
|
||||
normalizeOwnerType(targetEntry.ownerType) !== OWNER_TYPE_CHARACTER
|
||||
) {
|
||||
return { ok: false, reason: "unsupported-owner-type" };
|
||||
}
|
||||
|
||||
const scopeRewrite = rewriteScopedCharacterOwnerReferences(graph, sourceEntry, {
|
||||
ownerName: targetEntry.ownerName,
|
||||
ownerId: targetEntry.ownerName,
|
||||
});
|
||||
const characterNodeMerge = archiveOwnerCharacterNodesForMerge(
|
||||
graph,
|
||||
sourceEntry,
|
||||
targetEntry,
|
||||
);
|
||||
const mergedEntry = mergeKnowledgeOwnerEntries(
|
||||
createDefaultKnowledgeOwnerState({
|
||||
...targetEntry,
|
||||
nodeId: targetEntry.nodeId || characterNodeMerge.adoptedNodeId || sourceEntry.nodeId,
|
||||
aliases: uniqueStrings([
|
||||
...(targetEntry.aliases || []),
|
||||
...(sourceEntry.aliases || []),
|
||||
targetEntry.ownerName,
|
||||
sourceEntry.ownerName,
|
||||
]),
|
||||
updatedAt: Date.now(),
|
||||
lastSource: "manual-owner-merge",
|
||||
}),
|
||||
createDefaultKnowledgeOwnerState({
|
||||
...sourceEntry,
|
||||
ownerKey: targetEntry.ownerKey,
|
||||
ownerName: targetEntry.ownerName,
|
||||
nodeId: targetEntry.nodeId || characterNodeMerge.adoptedNodeId || sourceEntry.nodeId,
|
||||
aliases: uniqueStrings([
|
||||
...(targetEntry.aliases || []),
|
||||
...(sourceEntry.aliases || []),
|
||||
targetEntry.ownerName,
|
||||
sourceEntry.ownerName,
|
||||
]),
|
||||
updatedAt: Date.now(),
|
||||
lastSource: "manual-owner-merge",
|
||||
}),
|
||||
);
|
||||
|
||||
graph.knowledgeState.owners[targetEntry.ownerKey] = mergedEntry;
|
||||
delete graph.knowledgeState.owners[sourceEntry.ownerKey];
|
||||
|
||||
updateHistoryOwnerReferences(
|
||||
graph.historyState,
|
||||
sourceEntry,
|
||||
createDefaultKnowledgeOwnerState(graph.knowledgeState.owners[targetEntry.ownerKey]),
|
||||
);
|
||||
graph.knowledgeState = normalizeKnowledgeState(graph.knowledgeState, graph);
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: targetEntry.ownerKey,
|
||||
sourceOwnerKey: sourceEntry.ownerKey,
|
||||
archivedCharacterNodeIds: characterNodeMerge.archivedNodeIds,
|
||||
adoptedCharacterNodeId: characterNodeMerge.adoptedNodeId || "",
|
||||
updatedPovNodeIds: scopeRewrite.changedNodeIds,
|
||||
updatedPovEdgeCount: scopeRewrite.changedEdgeCount,
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteKnowledgeOwner(
|
||||
graph,
|
||||
ownerKey = "",
|
||||
{ mode = "owner-only" } = {},
|
||||
) {
|
||||
normalizeGraphCognitiveState(graph);
|
||||
const normalizedOwnerKey = normalizeString(ownerKey);
|
||||
const normalizedMode = normalizeString(mode) || "owner-only";
|
||||
if (!normalizedOwnerKey) {
|
||||
return { ok: false, reason: "missing-owner-key" };
|
||||
}
|
||||
if (
|
||||
!["owner-only", "archive-character", "archive-all"].includes(
|
||||
normalizedMode,
|
||||
)
|
||||
) {
|
||||
return { ok: false, reason: "invalid-delete-mode" };
|
||||
}
|
||||
|
||||
const sourceEntry = createDefaultKnowledgeOwnerState(
|
||||
graph?.knowledgeState?.owners?.[normalizedOwnerKey] || {},
|
||||
);
|
||||
if (!sourceEntry.ownerKey) {
|
||||
return { ok: false, reason: "owner-not-found" };
|
||||
}
|
||||
if (normalizeOwnerType(sourceEntry.ownerType) !== OWNER_TYPE_CHARACTER) {
|
||||
return { ok: false, reason: "unsupported-owner-type" };
|
||||
}
|
||||
|
||||
const archivedCharacterNodeIds =
|
||||
normalizedMode === "archive-character" || normalizedMode === "archive-all"
|
||||
? archiveCharacterNodesForOwner(graph, sourceEntry)
|
||||
: [];
|
||||
const archivedPovNodeIds =
|
||||
normalizedMode === "archive-all"
|
||||
? archivePovNodesForOwner(graph, sourceEntry)
|
||||
: [];
|
||||
|
||||
delete graph.knowledgeState.owners[normalizedOwnerKey];
|
||||
updateHistoryOwnerReferences(graph.historyState, sourceEntry, null);
|
||||
graph.knowledgeState = normalizeKnowledgeState(graph.knowledgeState, graph);
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: normalizedOwnerKey,
|
||||
mode: normalizedMode,
|
||||
archivedCharacterNodeIds,
|
||||
archivedPovNodeIds,
|
||||
};
|
||||
}
|
||||
|
||||
export function getKnowledgeOwnerEntry(graph, ownerKey = "") {
|
||||
normalizeGraphCognitiveState(graph);
|
||||
const normalizedOwnerKey = normalizeString(ownerKey);
|
||||
|
||||
89
index.js
89
index.js
@@ -271,6 +271,9 @@ import { DEFAULT_NODE_SCHEMA, validateSchema } from "./graph/schema.js";
|
||||
import {
|
||||
applyManualKnowledgeOverride,
|
||||
clearManualKnowledgeOverride,
|
||||
deleteKnowledgeOwner,
|
||||
mergeKnowledgeOwners,
|
||||
renameKnowledgeOwner,
|
||||
setManualActiveRegion,
|
||||
updateRegionAdjacencyManual,
|
||||
} from "./graph/knowledge-state.js";
|
||||
@@ -17689,6 +17692,89 @@ function onClearPanelKnowledgeOverride(payload = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function onRenamePanelKnowledgeOwner(payload = {}) {
|
||||
const ownerKey = String(payload.ownerKey || "").trim();
|
||||
const nextName = String(payload.nextName || "").trim();
|
||||
if (!currentGraph || !ownerKey || !nextName) {
|
||||
return { ok: false, error: "invalid-payload" };
|
||||
}
|
||||
if (!ensureGraphMutationReady("角色认知重命名", { notify: false })) {
|
||||
return { ok: false, error: "graph-write-blocked" };
|
||||
}
|
||||
|
||||
const result = renameKnowledgeOwner(currentGraph, ownerKey, nextName);
|
||||
if (!result?.ok) {
|
||||
return { ok: false, error: result?.reason || "rename-owner-failed" };
|
||||
}
|
||||
|
||||
const persist = saveGraphToChat({ reason: "panel-knowledge-owner-rename" });
|
||||
refreshPanelLiveState();
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: result.ownerKey || ownerKey,
|
||||
previousOwnerKey: result.previousOwnerKey || ownerKey,
|
||||
persist,
|
||||
persistBlocked: Boolean(persist?.blocked),
|
||||
};
|
||||
}
|
||||
|
||||
function onMergePanelKnowledgeOwners(payload = {}) {
|
||||
const sourceOwnerKey = String(payload.sourceOwnerKey || payload.ownerKey || "").trim();
|
||||
const targetOwnerKey = String(payload.targetOwnerKey || "").trim();
|
||||
if (!currentGraph || !sourceOwnerKey || !targetOwnerKey) {
|
||||
return { ok: false, error: "invalid-payload" };
|
||||
}
|
||||
if (!ensureGraphMutationReady("角色认知合并", { notify: false })) {
|
||||
return { ok: false, error: "graph-write-blocked" };
|
||||
}
|
||||
|
||||
const result = mergeKnowledgeOwners(currentGraph, {
|
||||
sourceOwnerKey,
|
||||
targetOwnerKey,
|
||||
});
|
||||
if (!result?.ok) {
|
||||
return { ok: false, error: result?.reason || "merge-owner-failed" };
|
||||
}
|
||||
|
||||
const persist = saveGraphToChat({ reason: "panel-knowledge-owner-merge" });
|
||||
refreshPanelLiveState();
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: result.ownerKey || targetOwnerKey,
|
||||
sourceOwnerKey: result.sourceOwnerKey || sourceOwnerKey,
|
||||
persist,
|
||||
persistBlocked: Boolean(persist?.blocked),
|
||||
};
|
||||
}
|
||||
|
||||
function onDeletePanelKnowledgeOwner(payload = {}) {
|
||||
const ownerKey = String(payload.ownerKey || "").trim();
|
||||
const mode = String(payload.mode || "owner-only").trim() || "owner-only";
|
||||
if (!currentGraph || !ownerKey) {
|
||||
return { ok: false, error: "invalid-payload" };
|
||||
}
|
||||
if (!ensureGraphMutationReady("角色认知删除", { notify: false })) {
|
||||
return { ok: false, error: "graph-write-blocked" };
|
||||
}
|
||||
|
||||
const result = deleteKnowledgeOwner(currentGraph, ownerKey, { mode });
|
||||
if (!result?.ok) {
|
||||
return { ok: false, error: result?.reason || "delete-owner-failed" };
|
||||
}
|
||||
|
||||
const persist = saveGraphToChat({
|
||||
reason: `panel-knowledge-owner-delete-${result.mode || mode}`,
|
||||
});
|
||||
refreshPanelLiveState();
|
||||
return {
|
||||
ok: true,
|
||||
ownerKey: result.ownerKey || ownerKey,
|
||||
mode: result.mode || mode,
|
||||
persist,
|
||||
persistBlocked: Boolean(persist?.blocked),
|
||||
};
|
||||
}
|
||||
|
||||
function onSetPanelActiveRegion(payload = {}) {
|
||||
const region = String(payload.region || "").trim();
|
||||
if (!currentGraph) {
|
||||
@@ -18681,6 +18767,9 @@ async function onCompactLukerSidecar() {
|
||||
deleteGraphNode: onDeletePanelGraphNode,
|
||||
applyKnowledgeOverride: onApplyPanelKnowledgeOverride,
|
||||
clearKnowledgeOverride: onClearPanelKnowledgeOverride,
|
||||
renameKnowledgeOwner: onRenamePanelKnowledgeOwner,
|
||||
mergeKnowledgeOwners: onMergePanelKnowledgeOwners,
|
||||
deleteKnowledgeOwner: onDeletePanelKnowledgeOwner,
|
||||
setActiveRegion: onSetPanelActiveRegion,
|
||||
setActiveStoryTime: onSetPanelActiveStoryTime,
|
||||
clearActiveStoryTime: onClearPanelActiveStoryTime,
|
||||
|
||||
@@ -5,9 +5,12 @@ import {
|
||||
applyCognitionUpdates,
|
||||
applyManualKnowledgeOverride,
|
||||
clearManualKnowledgeOverride,
|
||||
deleteKnowledgeOwner,
|
||||
mergeKnowledgeOwners,
|
||||
applyRegionUpdates,
|
||||
computeKnowledgeGateForNode,
|
||||
listKnowledgeOwners,
|
||||
renameKnowledgeOwner,
|
||||
resolveActiveRegionContext,
|
||||
resolveAdjacentRegions,
|
||||
resolveKnowledgeOwner,
|
||||
@@ -289,4 +292,305 @@ assert.equal(
|
||||
true,
|
||||
);
|
||||
|
||||
const renameGraph = createEmptyGraph();
|
||||
const renameCharacter = createNode({
|
||||
type: "character",
|
||||
fields: { name: "艾琳", state: "守塔人" },
|
||||
seq: 1,
|
||||
});
|
||||
const renameObjectiveEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "塔楼晨钟", summary: "晨钟再次响起" },
|
||||
seq: 2,
|
||||
});
|
||||
const renamePovMemory = createNode({
|
||||
type: "pov_memory",
|
||||
fields: { summary: "艾琳记得晨钟响起" },
|
||||
seq: 3,
|
||||
scope: {
|
||||
layer: "pov",
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
ownerId: "艾琳",
|
||||
},
|
||||
});
|
||||
addNode(renameGraph, renameCharacter);
|
||||
addNode(renameGraph, renameObjectiveEvent);
|
||||
addNode(renameGraph, renamePovMemory);
|
||||
applyCognitionUpdates(
|
||||
renameGraph,
|
||||
[
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
ownerNodeId: renameCharacter.id,
|
||||
knownRefs: [renameObjectiveEvent.id],
|
||||
visibility: [{ ref: renameObjectiveEvent.id, score: 1 }],
|
||||
},
|
||||
],
|
||||
{ changedNodeIds: [renameObjectiveEvent.id] },
|
||||
);
|
||||
const renameOwner = resolveKnowledgeOwner(renameGraph, {
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
nodeId: renameCharacter.id,
|
||||
});
|
||||
renameGraph.historyState.activeCharacterPovOwner = "艾琳";
|
||||
renameGraph.historyState.activeRecallOwnerKey = renameOwner.ownerKey;
|
||||
renameGraph.historyState.recentRecallOwnerKeys = [renameOwner.ownerKey];
|
||||
const renameResult = renameKnowledgeOwner(renameGraph, renameOwner.ownerKey, "艾琳娜");
|
||||
assert.equal(renameResult.ok, true);
|
||||
assert.equal(renameCharacter.fields.name, "艾琳娜");
|
||||
assert.equal(renamePovMemory.scope.ownerName, "艾琳娜");
|
||||
assert.equal(renamePovMemory.scope.ownerId, "艾琳娜");
|
||||
assert.equal(renameGraph.historyState.activeCharacterPovOwner, "艾琳娜");
|
||||
assert.equal(renameGraph.historyState.activeRecallOwnerKey, renameResult.ownerKey);
|
||||
assert.equal(renameGraph.knowledgeState.owners[renameOwner.ownerKey], undefined);
|
||||
assert.equal(renameGraph.knowledgeState.owners[renameResult.ownerKey].ownerName, "艾琳娜");
|
||||
assert.equal(
|
||||
renameGraph.knowledgeState.owners[renameResult.ownerKey].aliases.includes("艾琳"),
|
||||
true,
|
||||
);
|
||||
|
||||
const mergeGraph = createEmptyGraph();
|
||||
const mergeSourceCharacter = createNode({
|
||||
type: "character",
|
||||
fields: { name: "艾琳", state: "旧身份" },
|
||||
seq: 1,
|
||||
});
|
||||
const mergeTargetCharacter = createNode({
|
||||
type: "character",
|
||||
fields: { name: "艾琳娜", state: "新身份" },
|
||||
seq: 2,
|
||||
});
|
||||
const mergeSourceEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "旧钟楼记忆", summary: "她想起了旧钟楼" },
|
||||
seq: 3,
|
||||
});
|
||||
const mergeTargetEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "新花园记忆", summary: "她想起了新花园" },
|
||||
seq: 4,
|
||||
});
|
||||
const mergeSourcePov = createNode({
|
||||
type: "pov_memory",
|
||||
fields: { summary: "艾琳的 POV 记忆" },
|
||||
seq: 5,
|
||||
scope: {
|
||||
layer: "pov",
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
ownerId: "艾琳",
|
||||
},
|
||||
});
|
||||
addNode(mergeGraph, mergeSourceCharacter);
|
||||
addNode(mergeGraph, mergeTargetCharacter);
|
||||
addNode(mergeGraph, mergeSourceEvent);
|
||||
addNode(mergeGraph, mergeTargetEvent);
|
||||
addNode(mergeGraph, mergeSourcePov);
|
||||
applyCognitionUpdates(
|
||||
mergeGraph,
|
||||
[
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
ownerNodeId: mergeSourceCharacter.id,
|
||||
knownRefs: [mergeSourceEvent.id],
|
||||
visibility: [{ ref: mergeSourceEvent.id, score: 0.95 }],
|
||||
},
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳娜",
|
||||
ownerNodeId: mergeTargetCharacter.id,
|
||||
knownRefs: [mergeTargetEvent.id],
|
||||
visibility: [{ ref: mergeTargetEvent.id, score: 0.9 }],
|
||||
},
|
||||
],
|
||||
{ changedNodeIds: [mergeSourceEvent.id, mergeTargetEvent.id] },
|
||||
);
|
||||
const mergeSourceOwner = resolveKnowledgeOwner(mergeGraph, {
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳",
|
||||
nodeId: mergeSourceCharacter.id,
|
||||
});
|
||||
const mergeTargetOwner = resolveKnowledgeOwner(mergeGraph, {
|
||||
ownerType: "character",
|
||||
ownerName: "艾琳娜",
|
||||
nodeId: mergeTargetCharacter.id,
|
||||
});
|
||||
mergeGraph.historyState.activeCharacterPovOwner = "艾琳";
|
||||
mergeGraph.historyState.activeRecallOwnerKey = mergeSourceOwner.ownerKey;
|
||||
mergeGraph.historyState.recentRecallOwnerKeys = [
|
||||
mergeSourceOwner.ownerKey,
|
||||
mergeTargetOwner.ownerKey,
|
||||
];
|
||||
const mergeResult = mergeKnowledgeOwners(mergeGraph, {
|
||||
sourceOwnerKey: mergeSourceOwner.ownerKey,
|
||||
targetOwnerKey: mergeTargetOwner.ownerKey,
|
||||
});
|
||||
assert.equal(mergeResult.ok, true);
|
||||
assert.equal(mergeGraph.knowledgeState.owners[mergeSourceOwner.ownerKey], undefined);
|
||||
assert.equal(mergeGraph.knowledgeState.owners[mergeTargetOwner.ownerKey].knownNodeIds.includes(mergeSourceEvent.id), true);
|
||||
assert.equal(mergeGraph.knowledgeState.owners[mergeTargetOwner.ownerKey].knownNodeIds.includes(mergeTargetEvent.id), true);
|
||||
assert.equal(mergeGraph.knowledgeState.owners[mergeTargetOwner.ownerKey].aliases.includes("艾琳"), true);
|
||||
assert.equal(mergeSourcePov.scope.ownerName, "艾琳娜");
|
||||
assert.equal(mergeSourcePov.scope.ownerId, "艾琳娜");
|
||||
assert.equal(mergeSourceCharacter.archived, true);
|
||||
assert.equal(mergeGraph.historyState.activeCharacterPovOwner, "艾琳娜");
|
||||
assert.equal(mergeGraph.historyState.activeRecallOwnerKey, mergeTargetOwner.ownerKey);
|
||||
|
||||
const deleteOwnerOnlyGraph = createEmptyGraph();
|
||||
const deleteOwnerOnlyCharacter = createNode({
|
||||
type: "character",
|
||||
fields: { name: "米娅", state: "书记官" },
|
||||
seq: 1,
|
||||
});
|
||||
const deleteOwnerOnlyEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "记录密报", summary: "米娅记录了一份密报" },
|
||||
seq: 2,
|
||||
});
|
||||
const deleteOwnerOnlyPov = createNode({
|
||||
type: "pov_memory",
|
||||
fields: { summary: "米娅的 POV" },
|
||||
seq: 3,
|
||||
scope: {
|
||||
layer: "pov",
|
||||
ownerType: "character",
|
||||
ownerName: "米娅",
|
||||
ownerId: "米娅",
|
||||
},
|
||||
});
|
||||
addNode(deleteOwnerOnlyGraph, deleteOwnerOnlyCharacter);
|
||||
addNode(deleteOwnerOnlyGraph, deleteOwnerOnlyEvent);
|
||||
addNode(deleteOwnerOnlyGraph, deleteOwnerOnlyPov);
|
||||
applyCognitionUpdates(
|
||||
deleteOwnerOnlyGraph,
|
||||
[
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "米娅",
|
||||
ownerNodeId: deleteOwnerOnlyCharacter.id,
|
||||
knownRefs: [deleteOwnerOnlyEvent.id],
|
||||
visibility: [{ ref: deleteOwnerOnlyEvent.id, score: 0.85 }],
|
||||
},
|
||||
],
|
||||
{ changedNodeIds: [deleteOwnerOnlyEvent.id] },
|
||||
);
|
||||
const deleteOwnerOnly = resolveKnowledgeOwner(deleteOwnerOnlyGraph, {
|
||||
ownerType: "character",
|
||||
ownerName: "米娅",
|
||||
nodeId: deleteOwnerOnlyCharacter.id,
|
||||
});
|
||||
const deleteOwnerOnlyResult = deleteKnowledgeOwner(deleteOwnerOnlyGraph, deleteOwnerOnly.ownerKey, {
|
||||
mode: "owner-only",
|
||||
});
|
||||
assert.equal(deleteOwnerOnlyResult.ok, true);
|
||||
assert.equal(deleteOwnerOnlyGraph.knowledgeState.owners[deleteOwnerOnly.ownerKey], undefined);
|
||||
assert.equal(deleteOwnerOnlyCharacter.archived, false);
|
||||
assert.equal(deleteOwnerOnlyPov.archived, false);
|
||||
|
||||
const deleteArchiveCharacterGraph = createEmptyGraph();
|
||||
const deleteArchiveCharacterNode = createNode({
|
||||
type: "character",
|
||||
fields: { name: "诺拉", state: "侍女" },
|
||||
seq: 1,
|
||||
});
|
||||
const deleteArchiveCharacterEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "诺拉送信", summary: "诺拉送出了一封信" },
|
||||
seq: 2,
|
||||
});
|
||||
const deleteArchiveCharacterPov = createNode({
|
||||
type: "pov_memory",
|
||||
fields: { summary: "诺拉的 POV" },
|
||||
seq: 3,
|
||||
scope: {
|
||||
layer: "pov",
|
||||
ownerType: "character",
|
||||
ownerName: "诺拉",
|
||||
ownerId: "诺拉",
|
||||
},
|
||||
});
|
||||
addNode(deleteArchiveCharacterGraph, deleteArchiveCharacterNode);
|
||||
addNode(deleteArchiveCharacterGraph, deleteArchiveCharacterEvent);
|
||||
addNode(deleteArchiveCharacterGraph, deleteArchiveCharacterPov);
|
||||
applyCognitionUpdates(
|
||||
deleteArchiveCharacterGraph,
|
||||
[
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "诺拉",
|
||||
ownerNodeId: deleteArchiveCharacterNode.id,
|
||||
knownRefs: [deleteArchiveCharacterEvent.id],
|
||||
visibility: [{ ref: deleteArchiveCharacterEvent.id, score: 0.82 }],
|
||||
},
|
||||
],
|
||||
{ changedNodeIds: [deleteArchiveCharacterEvent.id] },
|
||||
);
|
||||
const deleteArchiveCharacterOwner = resolveKnowledgeOwner(deleteArchiveCharacterGraph, {
|
||||
ownerType: "character",
|
||||
ownerName: "诺拉",
|
||||
nodeId: deleteArchiveCharacterNode.id,
|
||||
});
|
||||
const deleteArchiveCharacterResult = deleteKnowledgeOwner(
|
||||
deleteArchiveCharacterGraph,
|
||||
deleteArchiveCharacterOwner.ownerKey,
|
||||
{ mode: "archive-character" },
|
||||
);
|
||||
assert.equal(deleteArchiveCharacterResult.ok, true);
|
||||
assert.equal(deleteArchiveCharacterNode.archived, true);
|
||||
assert.equal(deleteArchiveCharacterPov.archived, false);
|
||||
|
||||
const deleteArchiveAllGraph = createEmptyGraph();
|
||||
const deleteArchiveAllCharacter = createNode({
|
||||
type: "character",
|
||||
fields: { name: "赛拉", state: "守卫" },
|
||||
seq: 1,
|
||||
});
|
||||
const deleteArchiveAllEvent = createNode({
|
||||
type: "event",
|
||||
fields: { title: "赛拉巡逻", summary: "赛拉完成了巡逻" },
|
||||
seq: 2,
|
||||
});
|
||||
const deleteArchiveAllPov = createNode({
|
||||
type: "pov_memory",
|
||||
fields: { summary: "赛拉的 POV" },
|
||||
seq: 3,
|
||||
scope: {
|
||||
layer: "pov",
|
||||
ownerType: "character",
|
||||
ownerName: "赛拉",
|
||||
ownerId: "赛拉",
|
||||
},
|
||||
});
|
||||
addNode(deleteArchiveAllGraph, deleteArchiveAllCharacter);
|
||||
addNode(deleteArchiveAllGraph, deleteArchiveAllEvent);
|
||||
addNode(deleteArchiveAllGraph, deleteArchiveAllPov);
|
||||
applyCognitionUpdates(
|
||||
deleteArchiveAllGraph,
|
||||
[
|
||||
{
|
||||
ownerType: "character",
|
||||
ownerName: "赛拉",
|
||||
ownerNodeId: deleteArchiveAllCharacter.id,
|
||||
knownRefs: [deleteArchiveAllEvent.id],
|
||||
visibility: [{ ref: deleteArchiveAllEvent.id, score: 0.88 }],
|
||||
},
|
||||
],
|
||||
{ changedNodeIds: [deleteArchiveAllEvent.id] },
|
||||
);
|
||||
const deleteArchiveAllOwner = resolveKnowledgeOwner(deleteArchiveAllGraph, {
|
||||
ownerType: "character",
|
||||
ownerName: "赛拉",
|
||||
nodeId: deleteArchiveAllCharacter.id,
|
||||
});
|
||||
const deleteArchiveAllResult = deleteKnowledgeOwner(deleteArchiveAllGraph, deleteArchiveAllOwner.ownerKey, {
|
||||
mode: "archive-all",
|
||||
});
|
||||
assert.equal(deleteArchiveAllResult.ok, true);
|
||||
assert.equal(deleteArchiveAllCharacter.archived, true);
|
||||
assert.equal(deleteArchiveAllPov.archived, true);
|
||||
|
||||
console.log("knowledge-state tests passed");
|
||||
|
||||
237
ui/panel.js
237
ui/panel.js
@@ -2778,6 +2778,56 @@ function _renderCogOwnerDetail(graph, loadInfo, canRender, targetEl) {
|
||||
const suppressedCount = new Set([...(ownerState.manualHiddenNodeIds || []), ...(ownerState.mistakenNodeIds || [])]).size;
|
||||
const disabledAttr = !selectedNode || writeBlocked ? "disabled" : "";
|
||||
const displayInfo = _getOwnerDisplayInfo(selectedOwner, collisionIndex);
|
||||
const isCharacterOwner = String(selectedOwner.ownerType || "") === "character";
|
||||
const ownerActionDisabledAttr = writeBlocked || !isCharacterOwner ? "disabled" : "";
|
||||
const mergeCandidates = _getCognitionOwnerCollection(graph).filter(
|
||||
(entry) =>
|
||||
String(entry?.ownerType || "") === "character" &&
|
||||
String(entry?.ownerKey || "") !== String(selectedOwner.ownerKey || ""),
|
||||
);
|
||||
const mergeOptions = mergeCandidates.length
|
||||
? mergeCandidates
|
||||
.map((entry) => {
|
||||
const targetDisplayInfo = _getOwnerDisplayInfo(entry, collisionIndex);
|
||||
return `<option value="${_escAttr(entry.ownerKey || "")}">${_escHtml(targetDisplayInfo.title)}</option>`;
|
||||
})
|
||||
.join("")
|
||||
: '<option value="">暂无可合并目标</option>';
|
||||
const mergeDisabledAttr =
|
||||
writeBlocked || !isCharacterOwner || mergeCandidates.length === 0 ? "disabled" : "";
|
||||
const ownerManagementSection = isCharacterOwner
|
||||
? `
|
||||
<div class="bme-cog-override-section">
|
||||
<div class="bme-cog-override-title">角色认知管理</div>
|
||||
<div class="bme-cog-space-row">
|
||||
<label>重命名角色认知</label>
|
||||
<input class="bme-config-input" type="text" data-bme-cognition-owner-rename-input
|
||||
placeholder="输入新的角色名称..." value="${_escHtml(selectedOwner.ownerName || "")}" ${ownerActionDisabledAttr} />
|
||||
<div class="bme-config-help" style="font-size:10px;margin-top:2px">会同步更新 owner 名称、角色节点名和 POV scope,并把旧名加入 aliases。</div>
|
||||
<button class="bme-cog-btn bme-cog-btn--known" type="button" data-bme-cognition-owner-action="rename" ${ownerActionDisabledAttr}>重命名</button>
|
||||
</div>
|
||||
<div class="bme-cog-space-row">
|
||||
<label>合并到其他角色认知</label>
|
||||
<select class="bme-config-input" data-bme-cognition-owner-merge-target ${mergeDisabledAttr}>${mergeOptions}</select>
|
||||
<div class="bme-config-help" style="font-size:10px;margin-top:2px">会把当前角色的 POV scope 改写到目标角色,并合并认知状态与 aliases。</div>
|
||||
<button class="bme-cog-btn bme-cog-btn--mistaken" type="button" data-bme-cognition-owner-action="merge" ${mergeDisabledAttr}>合并到目标角色</button>
|
||||
</div>
|
||||
<div class="bme-cog-space-row">
|
||||
<label>删除范围</label>
|
||||
<select class="bme-config-input" data-bme-cognition-owner-delete-mode ${ownerActionDisabledAttr}>
|
||||
<option value="owner-only">只删除 owner,保留角色节点与 POV</option>
|
||||
<option value="archive-character">删除 owner,并归档角色节点</option>
|
||||
<option value="archive-all">删除 owner,并归档角色节点与 POV 记忆</option>
|
||||
</select>
|
||||
<div class="bme-config-help" style="font-size:10px;margin-top:2px">删除前会再次确认;不会无提示直接删除。</div>
|
||||
<button class="bme-cog-btn bme-cog-btn--clear" type="button" data-bme-cognition-owner-action="delete" ${ownerActionDisabledAttr}>删除角色认知</button>
|
||||
</div>
|
||||
</div>`
|
||||
: `
|
||||
<div class="bme-cog-override-section">
|
||||
<div class="bme-cog-override-title">角色认知管理</div>
|
||||
<div class="bme-cog-override-status">当前条目不是角色 owner,暂不支持重命名、合并或删除。</div>
|
||||
</div>`;
|
||||
|
||||
const visChips = strongVisibleNames.length
|
||||
? strongVisibleNames.map((n) => `<span class="bme-cog-chip is-visible">${_escHtml(n)}</span>`).join("")
|
||||
@@ -2830,6 +2880,8 @@ function _renderCogOwnerDetail(graph, loadInfo, canRender, targetEl) {
|
||||
<div class="bme-cog-chip-wrap">${supChips}</div>
|
||||
</div>
|
||||
|
||||
${ownerManagementSection}
|
||||
|
||||
<div class="bme-cog-override-section">
|
||||
<div class="bme-cog-override-title">对当前选中节点做手动覆盖</div>
|
||||
<div class="bme-cog-override-status">${
|
||||
@@ -5311,7 +5363,155 @@ async function _runCognitionNodeOverrideAction(mode = "") {
|
||||
} else {
|
||||
toastr.success(successMap[mode] || "认知覆盖已更新", "ST-BME");
|
||||
}
|
||||
_refreshCognitionSurfaces();
|
||||
}
|
||||
|
||||
function _refreshCognitionSurfaces() {
|
||||
_refreshDashboard();
|
||||
_refreshCognitionWorkspace();
|
||||
_refreshMobileCognitionFull();
|
||||
}
|
||||
|
||||
async function _callAction(actionKey = "", payload = {}) {
|
||||
const handler = _actionHandlers?.[String(actionKey || "")];
|
||||
if (typeof handler !== "function") {
|
||||
return { ok: false, error: "missing-action-handler" };
|
||||
}
|
||||
const result = await handler(payload);
|
||||
_refreshCognitionSurfaces();
|
||||
return result;
|
||||
}
|
||||
|
||||
async function _runCognitionOwnerManagementAction(mode = "", triggerEl = null) {
|
||||
const graph = _getGraph?.();
|
||||
const ownerEntries = _getCognitionOwnerCollection(graph);
|
||||
const ownerEntry =
|
||||
ownerEntries.find((entry) => entry.ownerKey === currentCognitionOwnerKey) || null;
|
||||
if (!ownerEntry) {
|
||||
toastr.info("先选择一个角色,再管理认知条目", "ST-BME");
|
||||
return;
|
||||
}
|
||||
if (String(ownerEntry.ownerType || "") !== "character") {
|
||||
toastr.info("当前只支持角色 owner 的重命名、合并和删除", "ST-BME");
|
||||
return;
|
||||
}
|
||||
|
||||
const container =
|
||||
triggerEl?.closest?.(".bme-cog-owner-detail") ||
|
||||
document.getElementById("bme-cog-owner-detail") ||
|
||||
document.getElementById("bme-mobile-cog-owner-detail");
|
||||
const collisionIndex = _buildOwnerCollisionIndex(ownerEntries);
|
||||
const displayInfo = _getOwnerDisplayInfo(ownerEntry, collisionIndex);
|
||||
let result = null;
|
||||
|
||||
if (mode === "rename") {
|
||||
const input = container?.querySelector?.("[data-bme-cognition-owner-rename-input]");
|
||||
const nextName = String(input?.value || "").trim();
|
||||
if (!nextName) {
|
||||
toastr.info("先输入新的角色名称", "ST-BME");
|
||||
return;
|
||||
}
|
||||
if (nextName === String(ownerEntry.ownerName || "").trim()) {
|
||||
toastr.info("新名称与当前名称相同,无需重命名", "ST-BME");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!window.confirm(
|
||||
`确定将角色认知「${displayInfo.title}」重命名为「${nextName}」吗?\n\n这会同步更新 owner 名称、角色节点名和 POV scope。`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
result = await _actionHandlers.renameKnowledgeOwner?.({
|
||||
ownerKey: ownerEntry.ownerKey,
|
||||
nextName,
|
||||
});
|
||||
} else if (mode === "merge") {
|
||||
const select = container?.querySelector?.("[data-bme-cognition-owner-merge-target]");
|
||||
const targetOwnerKey = String(select?.value || "").trim();
|
||||
if (!targetOwnerKey) {
|
||||
toastr.info("先选择要合并到的目标角色", "ST-BME");
|
||||
return;
|
||||
}
|
||||
if (targetOwnerKey === ownerEntry.ownerKey) {
|
||||
toastr.info("不能把角色合并到自己", "ST-BME");
|
||||
return;
|
||||
}
|
||||
const targetEntry =
|
||||
ownerEntries.find((entry) => String(entry.ownerKey || "") === targetOwnerKey) || null;
|
||||
const targetDisplayInfo = targetEntry
|
||||
? _getOwnerDisplayInfo(targetEntry, collisionIndex)
|
||||
: { title: targetOwnerKey };
|
||||
if (
|
||||
!window.confirm(
|
||||
`确定将角色认知「${displayInfo.title}」合并到「${targetDisplayInfo.title}」吗?\n\n这会把当前角色的 POV scope 改写到目标角色,并合并认知状态。`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
result = await _actionHandlers.mergeKnowledgeOwners?.({
|
||||
sourceOwnerKey: ownerEntry.ownerKey,
|
||||
targetOwnerKey,
|
||||
});
|
||||
} else if (mode === "delete") {
|
||||
const select = container?.querySelector?.("[data-bme-cognition-owner-delete-mode]");
|
||||
const deleteMode = String(select?.value || "owner-only").trim() || "owner-only";
|
||||
const deleteModeLabelMap = {
|
||||
"owner-only": "只删除 owner,保留角色节点与 POV",
|
||||
"archive-character": "删除 owner,并归档角色节点",
|
||||
"archive-all": "删除 owner,并归档角色节点与 POV 记忆",
|
||||
};
|
||||
if (
|
||||
!window.confirm(
|
||||
`确定删除角色认知「${displayInfo.title}」吗?\n\n删除范围:${deleteModeLabelMap[deleteMode] || deleteMode}\n\n此操作会立即写回图谱。`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
result = await _actionHandlers.deleteKnowledgeOwner?.({
|
||||
ownerKey: ownerEntry.ownerKey,
|
||||
mode: deleteMode,
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result?.ok) {
|
||||
const messageMap = {
|
||||
"graph-write-blocked": "当前图谱还在保护写入阶段,请稍后再试",
|
||||
"owner-not-found": "没有找到这个角色的认知状态,请先让她参与一轮提取",
|
||||
"same-owner": "不能把角色合并到自己",
|
||||
"missing-owner-or-name": "缺少角色或新名称",
|
||||
"invalid-delete-mode": "删除范围无效,请重新选择",
|
||||
"unsupported-owner-type": "当前只支持角色 owner 操作",
|
||||
};
|
||||
toastr.error(messageMap[result?.error] || "角色认知操作失败", "ST-BME");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === "rename") {
|
||||
currentCognitionOwnerKey = String(result.ownerKey || ownerEntry.ownerKey || "").trim();
|
||||
} else if (mode === "merge") {
|
||||
currentCognitionOwnerKey = String(result.ownerKey || "").trim();
|
||||
} else if (mode === "delete") {
|
||||
currentCognitionOwnerKey = "";
|
||||
}
|
||||
|
||||
const successMap = {
|
||||
rename: "角色认知已重命名",
|
||||
merge: "角色认知已合并",
|
||||
delete: "角色认知已删除",
|
||||
};
|
||||
if (result.persistBlocked) {
|
||||
toastr.warning(
|
||||
`${successMap[mode] || "角色认知已更新"},但正式写回可能仍在等待图谱就绪`,
|
||||
"ST-BME",
|
||||
);
|
||||
} else {
|
||||
toastr.success(successMap[mode] || "角色认知已更新", "ST-BME");
|
||||
}
|
||||
|
||||
_refreshCognitionSurfaces();
|
||||
}
|
||||
|
||||
async function _applyManualActiveRegionFromDashboard(clear = false) {
|
||||
@@ -5834,6 +6034,43 @@ function _bindActions() {
|
||||
_refreshMobileCognitionFull();
|
||||
});
|
||||
|
||||
const cogOwnerDetail = document.getElementById("bme-cog-owner-detail");
|
||||
if (cogOwnerDetail && cogOwnerDetail.dataset.bmeOwnerActionsBound !== "true") {
|
||||
cogOwnerDetail.addEventListener("click", async (e) => {
|
||||
const ownerActionBtn = e.target.closest("[data-bme-cognition-owner-action]");
|
||||
if (!ownerActionBtn || ownerActionBtn.disabled) return;
|
||||
await _runCognitionOwnerManagementAction(
|
||||
String(ownerActionBtn.dataset.bmeCognitionOwnerAction || ""),
|
||||
ownerActionBtn,
|
||||
);
|
||||
});
|
||||
cogOwnerDetail.dataset.bmeOwnerActionsBound = "true";
|
||||
}
|
||||
|
||||
const mobileCogOwnerDetail = document.getElementById("bme-mobile-cog-owner-detail");
|
||||
if (
|
||||
mobileCogOwnerDetail &&
|
||||
mobileCogOwnerDetail.dataset.bmeOwnerActionsBound !== "true"
|
||||
) {
|
||||
mobileCogOwnerDetail.addEventListener("click", async (e) => {
|
||||
const ownerActionBtn = e.target.closest("[data-bme-cognition-owner-action]");
|
||||
if (ownerActionBtn && !ownerActionBtn.disabled) {
|
||||
await _runCognitionOwnerManagementAction(
|
||||
String(ownerActionBtn.dataset.bmeCognitionOwnerAction || ""),
|
||||
ownerActionBtn,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeActionBtn = e.target.closest("[data-bme-cognition-node-action]");
|
||||
if (!nodeActionBtn || nodeActionBtn.disabled) return;
|
||||
await _runCognitionNodeOverrideAction(
|
||||
String(nodeActionBtn.dataset.bmeCognitionNodeAction || ""),
|
||||
);
|
||||
});
|
||||
mobileCogOwnerDetail.dataset.bmeOwnerActionsBound = "true";
|
||||
}
|
||||
|
||||
// Dashboard 跳转认知视图
|
||||
document.getElementById("bme-cognition-jump-to-view")?.addEventListener("click", () => {
|
||||
_switchTab("dashboard");
|
||||
|
||||
Reference in New Issue
Block a user