mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Reorganize modules into layered directories
This commit is contained in:
352
graph/memory-scope.js
Normal file
352
graph/memory-scope.js
Normal file
@@ -0,0 +1,352 @@
|
||||
const MEMORY_SCOPE_LAYER = {
|
||||
OBJECTIVE: "objective",
|
||||
POV: "pov",
|
||||
};
|
||||
|
||||
const MEMORY_SCOPE_OWNER_TYPE = {
|
||||
NONE: "",
|
||||
CHARACTER: "character",
|
||||
USER: "user",
|
||||
};
|
||||
|
||||
export const DEFAULT_MEMORY_SCOPE = Object.freeze({
|
||||
layer: MEMORY_SCOPE_LAYER.OBJECTIVE,
|
||||
ownerType: MEMORY_SCOPE_OWNER_TYPE.NONE,
|
||||
ownerId: "",
|
||||
ownerName: "",
|
||||
regionPrimary: "",
|
||||
regionPath: [],
|
||||
regionSecondary: [],
|
||||
});
|
||||
|
||||
export const MEMORY_SCOPE_BUCKETS = Object.freeze({
|
||||
CHARACTER_POV: "characterPov",
|
||||
USER_POV: "userPov",
|
||||
OBJECTIVE_CURRENT_REGION: "objectiveCurrentRegion",
|
||||
OBJECTIVE_ADJACENT_REGION: "objectiveAdjacentRegion",
|
||||
OBJECTIVE_GLOBAL: "objectiveGlobal",
|
||||
OTHER_POV: "otherPov",
|
||||
});
|
||||
|
||||
export const DEFAULT_SCOPE_BUCKET_WEIGHTS = Object.freeze({
|
||||
[MEMORY_SCOPE_BUCKETS.CHARACTER_POV]: 1.25,
|
||||
[MEMORY_SCOPE_BUCKETS.USER_POV]: 1.05,
|
||||
[MEMORY_SCOPE_BUCKETS.OBJECTIVE_CURRENT_REGION]: 1.15,
|
||||
[MEMORY_SCOPE_BUCKETS.OBJECTIVE_ADJACENT_REGION]: 0.9,
|
||||
[MEMORY_SCOPE_BUCKETS.OBJECTIVE_GLOBAL]: 0.75,
|
||||
[MEMORY_SCOPE_BUCKETS.OTHER_POV]: 0.6,
|
||||
});
|
||||
|
||||
function normalizeString(value) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
function normalizeKey(value) {
|
||||
return normalizeString(value).toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeStringArray(values = []) {
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
for (const value of Array.isArray(values) ? values : [values]) {
|
||||
const normalized = normalizeString(value);
|
||||
const key = normalizeKey(normalized);
|
||||
if (!normalized || seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
result.push(normalized);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function normalizeOwnerType(layer, ownerType) {
|
||||
if (layer !== MEMORY_SCOPE_LAYER.POV) {
|
||||
return MEMORY_SCOPE_OWNER_TYPE.NONE;
|
||||
}
|
||||
if (
|
||||
ownerType === MEMORY_SCOPE_OWNER_TYPE.CHARACTER ||
|
||||
ownerType === MEMORY_SCOPE_OWNER_TYPE.USER
|
||||
) {
|
||||
return ownerType;
|
||||
}
|
||||
return MEMORY_SCOPE_OWNER_TYPE.NONE;
|
||||
}
|
||||
|
||||
function normalizeLayer(layer) {
|
||||
return layer === MEMORY_SCOPE_LAYER.POV
|
||||
? MEMORY_SCOPE_LAYER.POV
|
||||
: MEMORY_SCOPE_LAYER.OBJECTIVE;
|
||||
}
|
||||
|
||||
export function createDefaultMemoryScope(overrides = {}) {
|
||||
return normalizeMemoryScope(overrides);
|
||||
}
|
||||
|
||||
export function normalizeMemoryScope(scope = {}, defaults = {}) {
|
||||
const merged = {
|
||||
...DEFAULT_MEMORY_SCOPE,
|
||||
...(defaults || {}),
|
||||
...(scope || {}),
|
||||
};
|
||||
const layer = normalizeLayer(merged.layer);
|
||||
const ownerType = normalizeOwnerType(layer, normalizeString(merged.ownerType));
|
||||
const ownerId = ownerType
|
||||
? normalizeString(merged.ownerId || merged.ownerName)
|
||||
: "";
|
||||
const ownerName = ownerType ? normalizeString(merged.ownerName) : "";
|
||||
const regionPrimary = normalizeString(merged.regionPrimary);
|
||||
const regionPath = normalizeStringArray(merged.regionPath);
|
||||
const regionSecondary = normalizeStringArray(merged.regionSecondary);
|
||||
|
||||
return {
|
||||
layer,
|
||||
ownerType,
|
||||
ownerId,
|
||||
ownerName,
|
||||
regionPrimary,
|
||||
regionPath,
|
||||
regionSecondary,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeNodeMemoryScope(node, defaults = {}) {
|
||||
const scope = normalizeMemoryScope(node?.scope, defaults);
|
||||
if (node && typeof node === "object") {
|
||||
node.scope = scope;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
export function normalizeEdgeMemoryScope(edge, defaults = {}) {
|
||||
const scope = normalizeMemoryScope(edge?.scope, defaults);
|
||||
if (edge && typeof edge === "object") {
|
||||
edge.scope = scope;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
export function isPovScope(scope) {
|
||||
return normalizeMemoryScope(scope).layer === MEMORY_SCOPE_LAYER.POV;
|
||||
}
|
||||
|
||||
export function isObjectiveScope(scope) {
|
||||
return normalizeMemoryScope(scope).layer === MEMORY_SCOPE_LAYER.OBJECTIVE;
|
||||
}
|
||||
|
||||
export function getScopeOwnerKey(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
const ownerType = normalizeString(normalized.ownerType);
|
||||
const ownerId = normalizeKey(normalized.ownerId || normalized.ownerName);
|
||||
return ownerType && ownerId ? `${ownerType}:${ownerId}` : "";
|
||||
}
|
||||
|
||||
export function getScopeRegionTokens(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
return normalizeStringArray([
|
||||
normalized.regionPrimary,
|
||||
...normalized.regionPath,
|
||||
...normalized.regionSecondary,
|
||||
]);
|
||||
}
|
||||
|
||||
export function getScopeRegionKey(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
return normalizeString(normalized.regionPrimary);
|
||||
}
|
||||
|
||||
export function getScopeSummary(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
const regionTokens = getScopeRegionTokens(normalized);
|
||||
return {
|
||||
layer: normalized.layer,
|
||||
ownerType: normalized.ownerType,
|
||||
ownerId: normalized.ownerId,
|
||||
ownerName: normalized.ownerName,
|
||||
ownerKey: getScopeOwnerKey(normalized),
|
||||
regionPrimary: normalized.regionPrimary,
|
||||
regionKey: getScopeRegionKey(normalized),
|
||||
regionTokens,
|
||||
};
|
||||
}
|
||||
|
||||
export function matchesScopeOwner(scope, ownerType, ownerValue = "") {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
if (normalizeString(normalized.ownerType) !== normalizeString(ownerType)) {
|
||||
return false;
|
||||
}
|
||||
const target = normalizeKey(ownerValue);
|
||||
if (!target) {
|
||||
return Boolean(normalized.ownerType);
|
||||
}
|
||||
return [normalized.ownerId, normalized.ownerName]
|
||||
.map((value) => normalizeKey(value))
|
||||
.includes(target);
|
||||
}
|
||||
|
||||
export function isSameLatestScopeBucket(node, options = {}) {
|
||||
const scope = normalizeMemoryScope(options.scope);
|
||||
const targetType = normalizeString(options.type);
|
||||
const primaryKeyField = normalizeString(options.primaryKeyField || "name") || "name";
|
||||
const primaryKeyValue = normalizeString(options.primaryKeyValue);
|
||||
if (!node || normalizeString(node.type) !== targetType) return false;
|
||||
if (normalizeString(node?.fields?.[primaryKeyField]) !== primaryKeyValue) {
|
||||
return false;
|
||||
}
|
||||
return hasSameScopeIdentity(node?.scope, scope);
|
||||
}
|
||||
|
||||
export function hasSameScopeIdentity(a, b) {
|
||||
const scopeA = normalizeMemoryScope(a);
|
||||
const scopeB = normalizeMemoryScope(b);
|
||||
if (scopeA.layer !== scopeB.layer) return false;
|
||||
if (scopeA.layer === MEMORY_SCOPE_LAYER.POV) {
|
||||
return getScopeOwnerKey(scopeA) === getScopeOwnerKey(scopeB);
|
||||
}
|
||||
return normalizeKey(getScopeRegionKey(scopeA)) === normalizeKey(getScopeRegionKey(scopeB));
|
||||
}
|
||||
|
||||
export function canMergeScopedMemories(a, b) {
|
||||
const scopeA = normalizeMemoryScope(a?.scope || a);
|
||||
const scopeB = normalizeMemoryScope(b?.scope || b);
|
||||
if (scopeA.layer !== scopeB.layer) return false;
|
||||
|
||||
if (scopeA.layer === MEMORY_SCOPE_LAYER.POV) {
|
||||
const ownerKeyA = getScopeOwnerKey(scopeA);
|
||||
const ownerKeyB = getScopeOwnerKey(scopeB);
|
||||
return Boolean(ownerKeyA) && ownerKeyA === ownerKeyB;
|
||||
}
|
||||
|
||||
const regionA = normalizeKey(getScopeRegionKey(scopeA));
|
||||
const regionB = normalizeKey(getScopeRegionKey(scopeB));
|
||||
return regionA === regionB;
|
||||
}
|
||||
|
||||
export function classifyNodeScopeBucket(
|
||||
node,
|
||||
{
|
||||
activeCharacterPovOwner = "",
|
||||
activeUserPovOwner = "",
|
||||
activeRegion = "",
|
||||
enablePovMemory = true,
|
||||
enableRegionScopedObjective = true,
|
||||
} = {},
|
||||
) {
|
||||
const scope = normalizeMemoryScope(node?.scope);
|
||||
const normalizedActiveRegion = normalizeKey(activeRegion);
|
||||
|
||||
if (scope.layer === MEMORY_SCOPE_LAYER.POV) {
|
||||
if (!enablePovMemory) {
|
||||
return MEMORY_SCOPE_BUCKETS.OTHER_POV;
|
||||
}
|
||||
if (
|
||||
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.CHARACTER &&
|
||||
matchesScopeOwner(scope, MEMORY_SCOPE_OWNER_TYPE.CHARACTER, activeCharacterPovOwner)
|
||||
) {
|
||||
return MEMORY_SCOPE_BUCKETS.CHARACTER_POV;
|
||||
}
|
||||
if (
|
||||
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.USER &&
|
||||
matchesScopeOwner(scope, MEMORY_SCOPE_OWNER_TYPE.USER, activeUserPovOwner)
|
||||
) {
|
||||
return MEMORY_SCOPE_BUCKETS.USER_POV;
|
||||
}
|
||||
if (
|
||||
!normalizeString(activeCharacterPovOwner) &&
|
||||
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.CHARACTER
|
||||
) {
|
||||
return MEMORY_SCOPE_BUCKETS.CHARACTER_POV;
|
||||
}
|
||||
if (
|
||||
!normalizeString(activeUserPovOwner) &&
|
||||
scope.ownerType === MEMORY_SCOPE_OWNER_TYPE.USER
|
||||
) {
|
||||
return MEMORY_SCOPE_BUCKETS.USER_POV;
|
||||
}
|
||||
return MEMORY_SCOPE_BUCKETS.OTHER_POV;
|
||||
}
|
||||
|
||||
if (!enableRegionScopedObjective || !normalizedActiveRegion) {
|
||||
return MEMORY_SCOPE_BUCKETS.OBJECTIVE_GLOBAL;
|
||||
}
|
||||
|
||||
const regionPrimary = normalizeKey(scope.regionPrimary);
|
||||
if (regionPrimary && regionPrimary === normalizedActiveRegion) {
|
||||
return MEMORY_SCOPE_BUCKETS.OBJECTIVE_CURRENT_REGION;
|
||||
}
|
||||
|
||||
const tokens = getScopeRegionTokens(scope).map((value) => normalizeKey(value));
|
||||
if (tokens.includes(normalizedActiveRegion)) {
|
||||
return MEMORY_SCOPE_BUCKETS.OBJECTIVE_ADJACENT_REGION;
|
||||
}
|
||||
|
||||
return MEMORY_SCOPE_BUCKETS.OBJECTIVE_GLOBAL;
|
||||
}
|
||||
|
||||
export function resolveScopeBucketWeight(bucket, overrides = {}) {
|
||||
return Number(
|
||||
overrides?.[bucket] ?? DEFAULT_SCOPE_BUCKET_WEIGHTS[bucket] ?? 1,
|
||||
) || 1;
|
||||
}
|
||||
|
||||
export function describeScopeBucket(bucket) {
|
||||
switch (bucket) {
|
||||
case MEMORY_SCOPE_BUCKETS.CHARACTER_POV:
|
||||
return "角色 POV";
|
||||
case MEMORY_SCOPE_BUCKETS.USER_POV:
|
||||
return "用户 POV";
|
||||
case MEMORY_SCOPE_BUCKETS.OBJECTIVE_CURRENT_REGION:
|
||||
return "当前地区客观";
|
||||
case MEMORY_SCOPE_BUCKETS.OBJECTIVE_ADJACENT_REGION:
|
||||
return "邻近地区客观";
|
||||
case MEMORY_SCOPE_BUCKETS.OBJECTIVE_GLOBAL:
|
||||
return "全局客观";
|
||||
case MEMORY_SCOPE_BUCKETS.OTHER_POV:
|
||||
return "其他 POV";
|
||||
default:
|
||||
return normalizeString(bucket) || "未知作用域";
|
||||
}
|
||||
}
|
||||
|
||||
export function describeMemoryScope(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
const parts = [];
|
||||
parts.push(
|
||||
normalized.layer === MEMORY_SCOPE_LAYER.POV ? "POV" : "客观",
|
||||
);
|
||||
|
||||
if (normalized.ownerType) {
|
||||
const ownerLabel = normalized.ownerName || normalized.ownerId;
|
||||
parts.push(`${normalized.ownerType}:${ownerLabel || "未命名"}`);
|
||||
}
|
||||
|
||||
if (normalized.regionPrimary) {
|
||||
parts.push(`地区:${normalized.regionPrimary}`);
|
||||
}
|
||||
|
||||
return parts.join(" | ");
|
||||
}
|
||||
|
||||
export function buildScopeBadgeText(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
if (normalized.layer === MEMORY_SCOPE_LAYER.POV) {
|
||||
const ownerLabel = normalized.ownerName || normalized.ownerId || "POV";
|
||||
return normalized.ownerType === MEMORY_SCOPE_OWNER_TYPE.USER
|
||||
? `用户 POV · ${ownerLabel}`
|
||||
: `角色 POV · ${ownerLabel}`;
|
||||
}
|
||||
return normalized.regionPrimary ? `客观 · ${normalized.regionPrimary}` : "客观 · 全局";
|
||||
}
|
||||
|
||||
export function buildRegionLine(scope) {
|
||||
const normalized = normalizeMemoryScope(scope);
|
||||
const parts = [];
|
||||
if (normalized.regionPrimary) {
|
||||
parts.push(`主地区: ${normalized.regionPrimary}`);
|
||||
}
|
||||
if (normalized.regionPath.length > 0) {
|
||||
parts.push(`地区路径: ${normalized.regionPath.join(" / ")}`);
|
||||
}
|
||||
if (normalized.regionSecondary.length > 0) {
|
||||
parts.push(`次级地区: ${normalized.regionSecondary.join(", ")}`);
|
||||
}
|
||||
return parts.join(" | ");
|
||||
}
|
||||
Reference in New Issue
Block a user