mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
feat: Enhance entity manual override handling and improve tagline styling
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 '' %}
|
||||||
|
|||||||
Reference in New Issue
Block a user