Files
ST-Bionic-Memory-Ecology/ui/ui-label-formatter.js
2026-06-05 11:20:53 +00:00

146 lines
4.7 KiB
JavaScript

// UI-only label formatting for ST-BME.
//
// Keep this module on the frontend boundary: these labels are translated for
// human-facing UI only. Do not import it from prompt/model/persistence paths.
import { normalizeMemoryScope } from "../graph/memory-scope.js";
import {
normalizeStoryTime,
normalizeStoryTimeSpan,
} from "../graph/story-timeline.js";
import { t } from "../i18n/index.js";
const UI_MEMORY_SCOPE_LAYER = Object.freeze({
OBJECTIVE: "objective",
POV: "pov",
});
const UI_MEMORY_SCOPE_OWNER_TYPE = Object.freeze({
CHARACTER: "character",
USER: "user",
});
export function uiMemoryNodeTypeClass(type) {
switch (type) {
case "pov_memory":
case "character":
return "type-character";
case "event":
return "type-event";
case "location":
return "type-location";
case "rule":
return "type-rule";
case "thread":
return "type-thread";
default:
return "type-default";
}
}
export function uiTypeLabel(type) {
const normalized = String(type || "").trim();
if (!normalized) return "—";
const key = `memory.type.${normalized}`;
const translated = t(key);
return translated === key ? normalized : translated;
}
export function normalizeOwnerUiType(ownerType = "") {
const normalized = String(ownerType || "").trim();
if (normalized === "user") return "user";
if (normalized === "character") return "character";
return "";
}
export function uiOwnerTypeLabel(ownerType = "") {
const normalizedType = normalizeOwnerUiType(ownerType);
if (normalizedType === "user") return t("scope.owner.user");
if (normalizedType === "character") return t("scope.owner.character");
return "Owner";
}
export function uiScopeBadgeText(scope) {
const normalized = normalizeMemoryScope(scope);
if (normalized.layer === UI_MEMORY_SCOPE_LAYER.POV) {
const ownerLabel = normalized.ownerName || normalized.ownerId || "POV";
return normalized.ownerType === UI_MEMORY_SCOPE_OWNER_TYPE.USER
? t("scope.badge.userPov", { owner: ownerLabel })
: t("scope.badge.characterPov", { owner: ownerLabel });
}
return normalized.regionPrimary
? t("scope.badge.objectiveRegion", { region: normalized.regionPrimary })
: t("scope.badge.objectiveGlobal");
}
export function uiBuildRegionLine(scope) {
const normalized = normalizeMemoryScope(scope);
const regionPath = Array.isArray(normalized.regionPath)
? normalized.regionPath.filter(Boolean)
: [];
const regionSecondary = Array.isArray(normalized.regionSecondary)
? normalized.regionSecondary.filter(Boolean)
: [];
const parts = [];
if (normalized.regionPrimary) {
parts.push(t("scope.region.primary", { region: normalized.regionPrimary }));
}
if (regionPath.length > 0) {
parts.push(t("scope.region.path", { path: regionPath.join(" / ") }));
}
if (regionSecondary.length > 0) {
parts.push(t("scope.region.secondary", { regions: regionSecondary.join(", ") }));
}
return parts.join(" | ");
}
export function uiDescribeStoryTimeDisplay(storyTime = {}) {
const normalized = normalizeStoryTime(storyTime);
const parts = [];
if (normalized.arc) parts.push(normalized.arc);
if (normalized.chapter) parts.push(normalized.chapter);
if (normalized.scene) parts.push(normalized.scene);
return parts.join(" / ");
}
export function uiDescribeStoryTimeSpanDisplay(storyTimeSpan = {}) {
const normalized = normalizeStoryTimeSpan(storyTimeSpan);
const label =
normalized.startLabel &&
normalized.endLabel &&
normalized.startLabel !== normalized.endLabel
? `${normalized.startLabel}${normalized.endLabel}`
: normalized.startLabel || normalized.endLabel || "";
if (!label) {
return normalized.mixed ? t("storyTime.mixedTime") : "";
}
return normalized.mixed ? `${label} · ${t("storyTime.mixed")}` : label;
}
export function uiDescribeNodeStoryTimeDisplay(node = {}) {
return (
uiDescribeStoryTimeDisplay(node.storyTime) ||
uiDescribeStoryTimeSpanDisplay(node.storyTimeSpan) ||
""
);
}
export function uiBuildScopeMetaText(node = {}) {
const scope = normalizeMemoryScope(node?.scope);
const parts = [];
if (scope.layer === UI_MEMORY_SCOPE_LAYER.POV) {
const ownerLabel = scope.ownerName || scope.ownerId || t("scope.owner.unnamed");
parts.push(
scope.ownerType === UI_MEMORY_SCOPE_OWNER_TYPE.USER
? t("scope.meta.userPov", { owner: ownerLabel })
: t("scope.meta.characterPov", { owner: ownerLabel }),
);
}
const regionLine = uiBuildRegionLine(scope);
if (regionLine) parts.push(regionLine);
const storyTime = uiDescribeNodeStoryTimeDisplay(node);
if (storyTime) parts.push(t("storyTime.meta", { time: storyTime }));
return parts.join(" · ");
}