feat: Enhance entity manual override handling and improve tagline styling

This commit is contained in:
JB
2025-10-13 06:22:24 -07:00
parent eb78347a85
commit ded405ff70
3 changed files with 99 additions and 21 deletions
+96 -20
View File
@@ -1264,17 +1264,72 @@ const initPrepare = (root = document) => {
const buildOverrideLookup = () => { const buildOverrideLookup = () => {
const map = new Map(); const map = new Map();
const overrides = Array.isArray(entityState.manualOverrides) ? entityState.manualOverrides : []; const register = (entry, origin) => {
overrides.forEach((entry) => {
if (!entry || typeof entry !== "object") return; if (!entry || typeof entry !== "object") return;
const key = canonicalizeEntityKey(entry.normalized || entry.token || ""); const normalizedToken = entry.normalized || entry.token || "";
if (key) { const canonical = canonicalizeEntityKey(normalizedToken);
map.set(key, entry); const tokenLabel = entry.token || entry.normalized || "";
} const pronunciation = entry.pronunciation || "";
}); if (!canonical || !tokenLabel) return;
map.set(canonical, {
...entry,
origin,
token: tokenLabel,
normalized: normalizedToken,
pronunciation,
});
};
const pronunciationOverrides = Array.isArray(entityState.pronunciationOverrides)
? entityState.pronunciationOverrides
: [];
pronunciationOverrides.forEach((entry) => register(entry, entry?.source || "history"));
const manualOverrides = Array.isArray(entityState.manualOverrides) ? entityState.manualOverrides : [];
manualOverrides.forEach((entry) => register(entry, "manual"));
return map; return map;
}; };
const buildPossessivePreviewSamples = (pronText, tokenLabel) => {
const samples = new Set();
const base = (pronText || "").trim();
const fallback = (tokenLabel || "").trim();
if (base) {
samples.add(base);
} else if (fallback) {
samples.add(fallback);
}
const reference = (fallback || base).trim();
if (!reference) {
return Array.from(samples);
}
const root = (base || reference).trim();
if (!root) {
return Array.from(samples);
}
const lowerReference = reference.toLowerCase();
const lowerRoot = root.toLowerCase();
const endsWithApostrophe = lowerRoot.endsWith("'") || lowerRoot.endsWith("");
const endsWithApostropheS = lowerRoot.endsWith("'s") || lowerRoot.endsWith("s");
if (!endsWithApostropheS) {
const possessive = `${root}'s`;
const altPossessive = `${root}s`;
samples.add(possessive);
samples.add(altPossessive);
}
if ((lowerReference.endsWith("s") || lowerRoot.endsWith("s")) && !endsWithApostrophe) {
samples.add(`${root}'`);
samples.add(`${root}`);
}
return Array.from(samples);
};
const joinPossessivePreviewSamples = (pronText, tokenLabel) => {
const variants = buildPossessivePreviewSamples(pronText, tokenLabel);
return variants.join("\n").trim();
};
const setEntitiesLoading = (isLoading) => { const setEntitiesLoading = (isLoading) => {
if (!entitySummaryContainer) { if (!entitySummaryContainer) {
return; return;
@@ -1555,12 +1610,11 @@ const initPrepare = (root = document) => {
} }
} }
const normalizedKey = canonicalizeEntityKey(normalized || tokenLabel);
const overrideMeta = normalizedKey ? overrideLookup.get(normalizedKey) : null;
const overrideButton = item.querySelector('[data-role="entity-add-override"]'); const overrideButton = item.querySelector('[data-role="entity-add-override"]');
if (overrideButton) { if (overrideButton) {
const hasOverride = entityState.manualOverrides.some((entry) => {
const candidate = entry?.normalized || entry?.token || "";
return candidate && candidate.toLowerCase() === normalized.toLowerCase();
});
overrideButton.dataset.entityToken = entity.label || entity.token || ""; overrideButton.dataset.entityToken = entity.label || entity.token || "";
overrideButton.dataset.entityNormalized = normalized; overrideButton.dataset.entityNormalized = normalized;
overrideButton.dataset.entityCategory = options.groupKey; overrideButton.dataset.entityCategory = options.groupKey;
@@ -1576,22 +1630,39 @@ const initPrepare = (root = document) => {
if (entity.kind) { if (entity.kind) {
overrideButton.dataset.entityKind = entity.kind; overrideButton.dataset.entityKind = entity.kind;
} }
overrideButton.textContent = hasOverride ? "Edit manual override" : "Add manual override"; overrideButton.textContent = overrideMeta
if (hasOverride) { ? overrideMeta.origin === "manual"
? "Edit manual override"
: "Edit pronunciation override"
: "Add manual override";
if (overrideMeta) {
item.dataset.hasOverride = "true"; item.dataset.hasOverride = "true";
} else {
delete item.dataset.hasOverride;
} }
} }
const inlineOverride = item.querySelector('[data-role="inline-override"]'); const inlineOverride = item.querySelector('[data-role="inline-override"]');
if (inlineOverride) { if (inlineOverride) {
const override = overrideLookup.get(normalized.toLowerCase()); const override = overrideMeta;
if (override) { if (override) {
inlineOverride.hidden = false; inlineOverride.hidden = false;
item.dataset.hasOverride = "true"; item.dataset.hasOverride = "true";
item.dataset.overrideId = override.id || override.normalized || override.token || ""; item.dataset.overrideId = override.id || override.normalized || override.token || "";
if (override.origin) {
item.dataset.overrideSource = override.origin;
} else {
delete item.dataset.overrideSource;
}
inlineOverride.dataset.pendingToken = override.token || tokenLabel;
inlineOverride.dataset.pendingNormalized = override.normalized || normalized;
inlineOverride.dataset.pendingContext = override.context || inlineOverride.dataset.pendingContext || "";
inlineOverride.dataset.pendingCategory = options.groupKey;
inlineOverride.dataset.pendingKind = entity.kind || "";
const pronInput = inlineOverride.querySelector('[data-role="manual-override-pronunciation"]'); const pronInput = inlineOverride.querySelector('[data-role="manual-override-pronunciation"]');
if (pronInput) { if (pronInput) {
pronInput.value = override.pronunciation || ""; pronInput.value = override.pronunciation || "";
pronInput.placeholder = override.token || tokenLabel;
} }
const voiceSelect = inlineOverride.querySelector('[data-role="manual-override-voice"]'); const voiceSelect = inlineOverride.querySelector('[data-role="manual-override-voice"]');
if (voiceSelect) { if (voiceSelect) {
@@ -1599,7 +1670,8 @@ const initPrepare = (root = document) => {
} }
const previewButton = inlineOverride.querySelector('[data-role="speaker-preview"]'); const previewButton = inlineOverride.querySelector('[data-role="speaker-preview"]');
if (previewButton) { if (previewButton) {
previewButton.dataset.previewText = override.pronunciation || override.token || tokenLabel; const previewValue = joinPossessivePreviewSamples(override.pronunciation, override.token || tokenLabel);
previewButton.dataset.previewText = previewValue || override.token || tokenLabel;
previewButton.dataset.voice = override.voice || override.voice_profile || baseVoice || ""; previewButton.dataset.voice = override.voice || override.voice_profile || baseVoice || "";
previewButton.dataset.language = languageCode; previewButton.dataset.language = languageCode;
previewButton.dataset.speed = defaultSpeed; previewButton.dataset.speed = defaultSpeed;
@@ -1618,12 +1690,13 @@ const initPrepare = (root = document) => {
} else { } else {
inlineOverride.hidden = true; inlineOverride.hidden = true;
delete item.dataset.overrideId; delete item.dataset.overrideId;
delete item.dataset.overrideSource;
} }
} }
const previewButton = item.querySelector('[data-entity-preview="true"]'); const previewButton = item.querySelector('[data-entity-preview="true"]');
if (previewButton) { if (previewButton) {
const previewVoice = overrideLookup.get(normalized.toLowerCase())?.voice || baseVoice || ""; const previewVoice = overrideMeta?.voice || baseVoice || "";
const previewText = Array.isArray(entity.samples) && entity.samples.length const previewText = Array.isArray(entity.samples) && entity.samples.length
? typeof entity.samples[0] === "string" ? typeof entity.samples[0] === "string"
? entity.samples[0] ? entity.samples[0]
@@ -1761,9 +1834,9 @@ const initPrepare = (root = document) => {
const previewButton = node.querySelector('[data-role="speaker-preview"]'); const previewButton = node.querySelector('[data-role="speaker-preview"]');
if (previewButton) { if (previewButton) {
const previewVoice = override.voice || voiceSelect?.value || baseVoice || ""; const previewVoice = override.voice || voiceSelect?.value || baseVoice || "";
const previewText = override.pronunciation || override.token || ""; const previewText = joinPossessivePreviewSamples(override.pronunciation, override.token || override.normalized || "");
previewButton.dataset.overrideId = overrideId; previewButton.dataset.overrideId = overrideId;
previewButton.dataset.previewText = previewText; previewButton.dataset.previewText = previewText || override.pronunciation || override.token || "";
previewButton.dataset.voice = previewVoice; previewButton.dataset.voice = previewVoice;
previewButton.dataset.language = languageCode; previewButton.dataset.language = languageCode;
previewButton.dataset.speed = defaultSpeed; previewButton.dataset.speed = defaultSpeed;
@@ -2199,7 +2272,8 @@ const initPrepare = (root = document) => {
const previewVoice = state.previewVoice || baseVoice || ""; const previewVoice = state.previewVoice || baseVoice || "";
const applyDatasets = (button) => { const applyDatasets = (button) => {
if (!button) return; if (!button) return;
button.dataset.previewText = previewText; const previewValue = joinPossessivePreviewSamples(previewText, state.token);
button.dataset.previewText = previewValue || previewText || state.token || "";
button.dataset.voice = previewVoice; button.dataset.voice = previewVoice;
button.dataset.language = languageCode; button.dataset.language = languageCode;
button.dataset.speed = defaultSpeed; button.dataset.speed = defaultSpeed;
@@ -2396,7 +2470,9 @@ const initPrepare = (root = document) => {
const container = input.closest(".manual-override"); const container = input.closest(".manual-override");
const previewButton = container?.querySelector('[data-role="speaker-preview"]'); const previewButton = container?.querySelector('[data-role="speaker-preview"]');
if (previewButton) { if (previewButton) {
previewButton.dataset.previewText = input.value.trim() || input.placeholder || ""; const fallback = container?.dataset.token || input.placeholder || "";
const previewValue = joinPossessivePreviewSamples(input.value.trim(), fallback);
previewButton.dataset.previewText = previewValue || input.value.trim() || fallback;
} }
if (overrideId) { if (overrideId) {
markOverrideDirty(overrideId); markOverrideDirty(overrideId);
+2
View File
@@ -57,6 +57,8 @@ body {
.brand__tagline { .brand__tagline {
color: var(--muted); color: var(--muted);
font-size: 0.9rem; font-size: 0.9rem;
font-family: "Georgia", "Times New Roman", serif;
font-style: italic;
} }
.top-actions .btn { .top-actions .btn {
+1 -1
View File
@@ -13,7 +13,7 @@
<div class="brand"> <div class="brand">
<span class="brand__mark">🔊</span> <span class="brand__mark">🔊</span>
<span class="brand__name">abogen</span> <span class="brand__name">abogen</span>
<span class="brand__tagline">Audiobooks for humans, not desktops</span> <span class="brand__tagline">Verba audiuntur</span>
</div> </div>
<nav class="top-actions"> <nav class="top-actions">
{% set endpoint = request.endpoint or '' %} {% set endpoint = request.endpoint or '' %}