fix(sonar): remove unused variables and commented-out code
Removes 43 small Sonar findings (unused locals, dead helpers, a misread comment); behaviour unchanged. CHANGELOG entry was already swept into 9f78a15.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a60b7e555d
commit
1d65a4df7c
@ -2600,7 +2600,7 @@ def extract_pages(pdf_path: Path, out_dir: Path, render_dpi: int = 110,
|
|||||||
blocks: list[Block] = []
|
blocks: list[Block] = []
|
||||||
data = {} if extra_turn else page.get_text("dict")
|
data = {} if extra_turn else page.get_text("dict")
|
||||||
for bi, blk in enumerate(data.get("blocks", [])):
|
for bi, blk in enumerate(data.get("blocks", [])):
|
||||||
if blk.get("type") != 0: # 0 = text, 1 = image
|
if blk.get("type") != 0: # block type: text or image
|
||||||
continue
|
continue
|
||||||
texts, sizes, fonts, bolds = [], [], [], []
|
texts, sizes, fonts, bolds = [], [], [], []
|
||||||
for line in blk.get("lines", []):
|
for line in blk.get("lines", []):
|
||||||
|
|||||||
@ -348,7 +348,7 @@ def _nvidia_clone_request_audio(
|
|||||||
if audio_path is None:
|
if audio_path is None:
|
||||||
raise RuntimeError(f"reference voice not found in library: {voice}")
|
raise RuntimeError(f"reference voice not found in library: {voice}")
|
||||||
prompt_wav = _to_wav_24k(audio_path)
|
prompt_wav = _to_wav_24k(audio_path)
|
||||||
has_ref, saved_transcript = _read_reference_text(audio_path)
|
_, saved_transcript = _read_reference_text(audio_path)
|
||||||
prompt_transcript = str(reference_transcript or saved_transcript or "").strip()
|
prompt_transcript = str(reference_transcript or saved_transcript or "").strip()
|
||||||
if mode == "flow" and not prompt_transcript:
|
if mode == "flow" and not prompt_transcript:
|
||||||
raise RuntimeError("NVIDIA Magpie Flow requires the selected voice to have an exact saved reference transcript")
|
raise RuntimeError("NVIDIA Magpie Flow requires the selected voice to have an exact saved reference transcript")
|
||||||
|
|||||||
@ -2939,11 +2939,6 @@ def _build_compact_character_render_prompts(name: str, book: str, sheet: dict,
|
|||||||
f"The scalp, crown, hairline, {ear_shape}, and complete natural hairstyle are visibly exposed and unobstructed."
|
f"The scalp, crown, hairline, {ear_shape}, and complete natural hairstyle are visibly exposed and unobstructed."
|
||||||
)
|
)
|
||||||
bareheaded = not has_headwear
|
bareheaded = not has_headwear
|
||||||
head_lock = (
|
|
||||||
f"HEAD CONTINUITY: In all three figures, clearly show the exposed scalp, crown, hairline, {ear_shape}, and complete natural hairstyle in front, profile, and rear views. "
|
|
||||||
if bareheaded else
|
|
||||||
"HEADWEAR LOCK: reproduce only the explicitly established period headwear, identically in all three views. "
|
|
||||||
)
|
|
||||||
leather_only = _character_is_leather_only_armor(sheet)
|
leather_only = _character_is_leather_only_armor(sheet)
|
||||||
period_materials = (
|
period_materials = (
|
||||||
"wool, linen, leather, hand-worked fastenings, and flat stitched-leather footwear"
|
"wool, linen, leather, hand-worked fastenings, and flat stitched-leather footwear"
|
||||||
@ -3095,12 +3090,6 @@ def _build_compact_character_render_prompts(name: str, book: str, sheet: dict,
|
|||||||
f"{female_presentation_lock}"
|
f"{female_presentation_lock}"
|
||||||
f"{no_headwear}"
|
f"{no_headwear}"
|
||||||
)
|
)
|
||||||
if visual_style == "concept":
|
|
||||||
portrait_style = "One fixed semi-realistic hand-painted fantasy production artist with grounded anatomy and weathered materials."
|
|
||||||
sheet_style = portrait_style
|
|
||||||
else:
|
|
||||||
portrait_style = "Strict live-action photorealistic historical-fantasy production photography; real actor, honest skin, natural hair, restrained warm-neutral film color."
|
|
||||||
sheet_style = "Strict live-action photorealistic costume-continuity studio photography by that same production photographer, with a real actor and physically believable period materials."
|
|
||||||
# Nothing previously told the model what a correctly-proportioned body
|
# Nothing previously told the model what a correctly-proportioned body
|
||||||
# looks like, and the turnaround triptych in particular squeezes each
|
# looks like, and the turnaround triptych in particular squeezes each
|
||||||
# full-body figure into a narrow 512-wide third of the frame — confirmed
|
# full-body figure into a narrow 512-wide third of the frame — confirmed
|
||||||
@ -3112,7 +3101,7 @@ def _build_compact_character_render_prompts(name: str, book: str, sheet: dict,
|
|||||||
# same reasoning as the armor lock above: this encoder still visualizes a
|
# same reasoning as the armor lock above: this encoder still visualizes a
|
||||||
# noun mentioned after "no"/"not", so "adult proportions, not dwarfed"
|
# noun mentioned after "no"/"not", so "adult proportions, not dwarfed"
|
||||||
# would still cue dwarfism rather than prevent it.
|
# would still cue dwarfism rather than prevent it.
|
||||||
subject_clause, identical_noun, proportion_lock = _age_body_lock(sheet)
|
_, identical_noun, proportion_lock = _age_body_lock(sheet)
|
||||||
# Short and framing-first. The long version (~3 000 characters, framing rules buried in the middle)
|
# Short and framing-first. The long version (~3 000 characters, framing rules buried in the middle)
|
||||||
# was measured against this one on the same character, 5 seeds each, scored by the image check:
|
# was measured against this one on the same character, 5 seeds each, scored by the image check:
|
||||||
# full bodies 0/5 vs 5/5 on a 1536x1024 canvas (also 5/5 at 1536x1280), plain backdrop 2/5 vs 5/5.
|
# full bodies 0/5 vs 5/5 on a 1536x1024 canvas (also 5/5 at 1536x1280), plain backdrop 2/5 vs 5/5.
|
||||||
@ -4104,7 +4093,6 @@ def _comfyui_upload_data_uri(comfyui_url: str, data_uri: str) -> str:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(400, f"Identity image is not valid base64: {exc}")
|
raise HTTPException(400, f"Identity image is not valid base64: {exc}")
|
||||||
mime = header.split(";", 1)[0].split(":", 1)[-1].lower()
|
mime = header.split(";", 1)[0].split(":", 1)[-1].lower()
|
||||||
ext = ".jpg" if mime in {"image/jpeg", "image/jpg"} else ".webp" if mime == "image/webp" else ".png"
|
|
||||||
filename = f"voice_creator_identity_{uuid.uuid4().hex}.png"
|
filename = f"voice_creator_identity_{uuid.uuid4().hex}.png"
|
||||||
try:
|
try:
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
|
|||||||
@ -3006,7 +3006,7 @@ def _spelling_agent_job(doc_id: str, job_id: str, article_id: str | None, page:
|
|||||||
@router.post("/api/magazine/{doc_id}/spelling/agent")
|
@router.post("/api/magazine/{doc_id}/spelling/agent")
|
||||||
async def magazine_spelling_agent(doc_id: str, request: Request):
|
async def magazine_spelling_agent(doc_id: str, request: Request):
|
||||||
"""Background job: the LLM fixes provable OCR misspellings; the rest stays marked."""
|
"""Background job: the LLM fixes provable OCR misspellings; the rest stays marked."""
|
||||||
d, _ = _require_magazine(doc_id)
|
_, _ = _require_magazine(doc_id)
|
||||||
body = await request.json()
|
body = await request.json()
|
||||||
body = body if isinstance(body, dict) else {}
|
body = body if isinstance(body, dict) else {}
|
||||||
article = _article_id(str(body["article"])) if body.get("article") else None
|
article = _article_id(str(body["article"])) if body.get("article") else None
|
||||||
@ -3213,7 +3213,7 @@ def _audio_generate_one_sync(doc_id: str, job_id: str, article_id: str, voice: s
|
|||||||
async def magazine_generate_article_audio(doc_id: str, article_id: str, request: Request):
|
async def magazine_generate_article_audio(doc_id: str, article_id: str, request: Request):
|
||||||
"""Render one approved article to a single tagged MP3 (title/author/album,
|
"""Render one approved article to a single tagged MP3 (title/author/album,
|
||||||
its own text as embedded lyrics, cover art) -- for taking it out of the app."""
|
its own text as embedded lyrics, cover art) -- for taking it out of the app."""
|
||||||
d, meta = _require_magazine(doc_id)
|
_, meta = _require_magazine(doc_id)
|
||||||
article_id = _article_id(article_id)
|
article_id = _article_id(article_id)
|
||||||
body = await request.json() if await request.body() else {}
|
body = await request.json() if await request.body() else {}
|
||||||
voice = str(body.get("voice") or meta.get("default_voice") or "").strip()
|
voice = str(body.get("voice") or meta.get("default_voice") or "").strip()
|
||||||
@ -3484,7 +3484,6 @@ async def magazine_audio_zip(doc_id: str, provenance: str = "", voice: str = "")
|
|||||||
import tempfile
|
import tempfile
|
||||||
with tempfile.TemporaryDirectory(prefix="mag-prov-") as tagdir, zipfile.ZipFile(buf, "w", zipfile.ZIP_STORED) as zf:
|
with tempfile.TemporaryDirectory(prefix="mag-prov-") as tagdir, zipfile.ZipFile(buf, "w", zipfile.ZIP_STORED) as zf:
|
||||||
for i, a in enumerate(ordered, start=1):
|
for i, a in enumerate(ordered, start=1):
|
||||||
page = (a.get("pages") or [0])[0]
|
|
||||||
name = _article_download_filename(meta, a)
|
name = _article_download_filename(meta, a)
|
||||||
# Zero-padded sequence prefix so extracting the zip and sorting
|
# Zero-padded sequence prefix so extracting the zip and sorting
|
||||||
# by filename gives the reading order even where two articles
|
# by filename gives the reading order even where two articles
|
||||||
|
|||||||
@ -227,7 +227,6 @@ def _cast_to_md(data: dict) -> str:
|
|||||||
doesn't need to round-trip back through _md_to_cast (that function only
|
doesn't need to round-trip back through _md_to_cast (that function only
|
||||||
still matters for pre-migration legacy .md files)."""
|
still matters for pre-migration legacy .md files)."""
|
||||||
segments = data.get("segments") or []
|
segments = data.get("segments") or []
|
||||||
roster = data.get("roster") or []
|
|
||||||
saved_at = data.get("savedAt")
|
saved_at = data.get("savedAt")
|
||||||
saved_str = ""
|
saved_str = ""
|
||||||
if saved_at:
|
if saved_at:
|
||||||
|
|||||||
@ -25,16 +25,6 @@ async def get_settings(request: Request, response: Response):
|
|||||||
# install shows a real usable key in Settings immediately without a
|
# install shows a real usable key in Settings immediately without a
|
||||||
# migration step, whether or not the gate is actually turned on yet.
|
# migration step, whether or not the gate is actually turned on yet.
|
||||||
s["external_api_key"] = _ensure_external_api_key()
|
s["external_api_key"] = _ensure_external_api_key()
|
||||||
host = request.headers.get("host", "")
|
|
||||||
origin = request.headers.get("origin", "") or request.headers.get("referer", "")
|
|
||||||
try:
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
same_origin = (
|
|
||||||
request.headers.get("sec-fetch-site", "").lower() == "same-origin"
|
|
||||||
or bool(origin and host and urlparse(origin).netloc == host)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
same_origin = False
|
|
||||||
# Never return provider credentials to browser JavaScript. The mask lets
|
# Never return provider credentials to browser JavaScript. The mask lets
|
||||||
# the Settings UI indicate that a value exists without making it usable in
|
# the Settings UI indicate that a value exists without making it usable in
|
||||||
# client-side requests or browser storage.
|
# client-side requests or browser storage.
|
||||||
|
|||||||
12
static/dist/main.min.js
vendored
12
static/dist/main.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1789,7 +1789,6 @@ STRIKTE FORMAT- UND TEXTREGELN:
|
|||||||
const _abRedoStack = [];
|
const _abRedoStack = [];
|
||||||
const _abHistoryLimit = 40;
|
const _abHistoryLimit = 40;
|
||||||
const _abSyncTopbar = () => {
|
const _abSyncTopbar = () => {
|
||||||
const hasSelection = !_abBar.hidden;
|
|
||||||
const hasHistory = _abUndoStack.length > 0 || _abRedoStack.length > 0;
|
const hasHistory = _abUndoStack.length > 0 || _abRedoStack.length > 0;
|
||||||
_abEditToolbar.hidden = !hasHistory;
|
_abEditToolbar.hidden = !hasHistory;
|
||||||
// Always visible — it hosts the font-size controls now, not just the
|
// Always visible — it hosts the font-size controls now, not just the
|
||||||
@ -1803,16 +1802,6 @@ STRIKTE FORMAT- UND TEXTREGELN:
|
|||||||
return { key: 'segments', arr: [] };
|
return { key: 'segments', arr: [] };
|
||||||
};
|
};
|
||||||
const _abCloneSegments = (segs) => (segs || []).map(s => ({ ...s }));
|
const _abCloneSegments = (segs) => (segs || []).map(s => ({ ...s }));
|
||||||
const _abSyncRosterList = (segs) => {
|
|
||||||
const names = [];
|
|
||||||
for (const s of segs || []) {
|
|
||||||
|
|
||||||
const sp = s?.type === 'dialogue' && s.speaker ? s.speaker : '';
|
|
||||||
if (!sp || /^Narrator$/i.test(sp) || /^Unknown|Unbekannt/i.test(sp)) continue;
|
|
||||||
if (!names.some(n => n.toLowerCase() === sp.toLowerCase())) names.push(sp);
|
|
||||||
}
|
|
||||||
_audiobook.roster = names;
|
|
||||||
};
|
|
||||||
const _abPersistManualEdit = () => {
|
const _abPersistManualEdit = () => {
|
||||||
const active = _abActiveSegments();
|
const active = _abActiveSegments();
|
||||||
if (_audiobook.lastText) {
|
if (_audiobook.lastText) {
|
||||||
|
|||||||
@ -391,7 +391,6 @@ async function audiobookAutoRepairCast(threshold = 10, maxRounds = 4) {
|
|||||||
// uses the same bounded suspect-scored pass as the rounds above.
|
// uses the same bounded suspect-scored pass as the rounds above.
|
||||||
toast(`2.4 Still ${prev} unknown — running full verification pass…`, 'info');
|
toast(`2.4 Still ${prev} unknown — running full verification pass…`, 'info');
|
||||||
await audiobookRecastUnknown(null, null, { repairNarration: true });
|
await audiobookRecastUnknown(null, null, { repairNarration: true });
|
||||||
const afterVerify = countUnknown();
|
|
||||||
// 2.5 — consistency is a different kind of check (it re-judges lines already
|
// 2.5 — consistency is a different kind of check (it re-judges lines already
|
||||||
// attributed), so it runs last, once attributions have settled, and only if
|
// attributed), so it runs last, once attributions have settled, and only if
|
||||||
// the passes above didn't leave the cast in a worse state than they found it.
|
// the passes above didn't leave the cast in a worse state than they found it.
|
||||||
|
|||||||
@ -1629,24 +1629,6 @@ function csSourceBadgeHtml(s, field) {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function csField(label, value, sheet, field) {
|
|
||||||
if (!value) return '';
|
|
||||||
const badge = sheet && field ? csSourceBadgeHtml(sheet, field) : '';
|
|
||||||
const key = field ? ` data-sheet-key="${escHtml(field)}"` : '';
|
|
||||||
// Value text lives in its own span, separate from the source-citation
|
|
||||||
// badge buttons — the whole <dd> used to be the only option, which would
|
|
||||||
// have made the badges themselves editable/deletable text once this
|
|
||||||
// became contenteditable.
|
|
||||||
return `<div class="cs-row"><dt>${label}</dt><dd><span class="cs-field-editable"${key}>${escHtml(String(value))}</span>${badge ? ` ${badge}` : ''}</dd></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function csIdentityCell(label, value, sheet, field) {
|
|
||||||
if (!value) return '';
|
|
||||||
const badge = sheet && field ? csSourceBadgeHtml(sheet, field) : '';
|
|
||||||
const key = field ? ` data-sheet-key="${escHtml(field)}"` : '';
|
|
||||||
return `<div class="cs-id-cell"><dt>${label}</dt><dd><span class="cs-field-editable"${key}>${escHtml(String(value))}</span>${badge ? ` ${badge}` : ''}</dd></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function csPromptBox(label, value, sheetKey, emptyNote, extraImage) {
|
function csPromptBox(label, value, sheetKey, emptyNote, extraImage) {
|
||||||
const text = _csStr(value);
|
const text = _csStr(value);
|
||||||
const has = !!text.trim();
|
const has = !!text.trim();
|
||||||
@ -1692,20 +1674,6 @@ function csAvatarHtml(s) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function csIdentityTagList(s) {
|
|
||||||
const tags = [];
|
|
||||||
const add = (label, value) => {
|
|
||||||
const text = String(value || '').trim();
|
|
||||||
if (!text) return;
|
|
||||||
const key = text.toLowerCase();
|
|
||||||
if (tags.some(t => t.key === key)) return;
|
|
||||||
tags.push({ label, text, key });
|
|
||||||
};
|
|
||||||
const firstLast = [s.first_name, s.last_name].filter(Boolean).join(' ').trim();
|
|
||||||
if (firstLast && firstLast !== String(s.name || '').trim()) add('first / last', firstLast);
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
function csNameHue(name) {
|
function csNameHue(name) {
|
||||||
return Math.abs((name || '?').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) % 360, 0));
|
return Math.abs((name || '?').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) % 360, 0));
|
||||||
}
|
}
|
||||||
@ -2203,7 +2171,7 @@ function csWireResultInteractions(root, sheets, sourceText, book, inline = false
|
|||||||
// editing two fields within 900ms can't cancel each other's still-pending
|
// editing two fields within 900ms can't cancel each other's still-pending
|
||||||
// save the way a single shared timer did elsewhere in this app earlier
|
// save the way a single shared timer did elsewhere in this app earlier
|
||||||
// this session). Fields that are currently empty aren't rendered at all
|
// this session). Fields that are currently empty aren't rendered at all
|
||||||
// (csField/csIdentityCell skip blank values), so adding a brand-new value
|
// (blank values are not rendered), so adding a brand-new value
|
||||||
// to a field that has nothing yet still needs the full Library profile
|
// to a field that has nothing yet still needs the full Library profile
|
||||||
// page — this only covers correcting a value that's already shown.
|
// page — this only covers correcting a value that's already shown.
|
||||||
root.querySelectorAll('.cs-edit-toggle').forEach(btn => {
|
root.querySelectorAll('.cs-edit-toggle').forEach(btn => {
|
||||||
@ -2592,27 +2560,10 @@ async function csForReaderSelective(selectedNames) {
|
|||||||
// that race regardless of which async step caused it.
|
// that race regardless of which async step caused it.
|
||||||
if (typeof showReaderView === 'function') setTimeout(() => showReaderView('chars'), 120);
|
if (typeof showReaderView === 'function') setTimeout(() => showReaderView('chars'), 120);
|
||||||
const pageHost = csReaderPageHost();
|
const pageHost = csReaderPageHost();
|
||||||
const knownRoster = csKnownReaderRoster();
|
|
||||||
let records = [];
|
let records = [];
|
||||||
try { records = (typeof clGetAllByTagOrBook === 'function') ? await clGetAllByTagOrBook(book) : []; } catch (_) { records = []; }
|
try { records = (typeof clGetAllByTagOrBook === 'function') ? await clGetAllByTagOrBook(book) : []; } catch (_) { records = []; }
|
||||||
const picked = [];
|
const picked = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const matchesWanted = (rec) => {
|
|
||||||
const tokens = csNameTokens({
|
|
||||||
name: rec?.name || '',
|
|
||||||
aliases: rec?.sheet?.aliases || '',
|
|
||||||
first_name: rec?.sheet?.first_name || '',
|
|
||||||
last_name: rec?.sheet?.last_name || '',
|
|
||||||
full_name: rec?.sheet?.full_name || '',
|
|
||||||
});
|
|
||||||
for (const t of tokens) {
|
|
||||||
const lower = String(t || '').toLowerCase();
|
|
||||||
for (const w of wanted) {
|
|
||||||
if (lower === w || lower.includes(w) || w.includes(lower)) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
// Exact canonical names always win. Alias matching is only a fallback for
|
// Exact canonical names always win. Alias matching is only a fallback for
|
||||||
// old records that have no exact record; otherwise selecting Lysandra could
|
// old records that have no exact record; otherwise selecting Lysandra could
|
||||||
// pick an Alrik profile whose generated aliases happened to contain the
|
// pick an Alrik profile whose generated aliases happened to contain the
|
||||||
|
|||||||
@ -1241,7 +1241,7 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
|
|||||||
const turnN = turnCount;
|
const turnN = turnCount;
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
|
|
||||||
const userBubble = addBubble('user', text);
|
addBubble('user', text);
|
||||||
const assistantBubble = addTypingBubble();
|
const assistantBubble = addTypingBubble();
|
||||||
convCurrentSentenceBubble = assistantBubble;
|
convCurrentSentenceBubble = assistantBubble;
|
||||||
let assistantText = '';
|
let assistantText = '';
|
||||||
|
|||||||
@ -207,7 +207,7 @@ function initElevenLabsBrowser() {
|
|||||||
const e = await r.json().catch(() => ({}));
|
const e = await r.json().catch(() => ({}));
|
||||||
throw new Error(e.detail || r.statusText);
|
throw new Error(e.detail || r.statusText);
|
||||||
}
|
}
|
||||||
const d = await r.json();
|
await r.json();
|
||||||
btn.innerHTML = '<span class="mdi mdi-check"></span> In library';
|
btn.innerHTML = '<span class="mdi mdi-check"></span> In library';
|
||||||
btn.classList.add('el-imported');
|
btn.classList.add('el-imported');
|
||||||
if (typeof toast === 'function') toast(`Imported "${name}" — transcript and benchmark are preparing in the background`, 'success');
|
if (typeof toast === 'function') toast(`Imported "${name}" — transcript and benchmark are preparing in the background`, 'success');
|
||||||
|
|||||||
@ -808,29 +808,6 @@ function _charGenderLabel(sh) {
|
|||||||
return gender.charAt(0).toUpperCase() + gender.slice(1);
|
return gender.charAt(0).toUpperCase() + gender.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _charRelsHtml(rec, allChars) {
|
|
||||||
if (!allChars || allChars.length < 2) return '';
|
|
||||||
const relText = _libStr(rec.sheet?.relationships).toLowerCase();
|
|
||||||
if (!relText) return '';
|
|
||||||
const hits = allChars
|
|
||||||
.filter(function (c) { return c.id !== rec.id && (c.name || '').length > 1; })
|
|
||||||
.map(function (c) {
|
|
||||||
const re = new RegExp(c.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
||||||
return { c: c, n: (relText.match(re) || []).length };
|
|
||||||
})
|
|
||||||
.filter(function (x) { return x.n > 0; })
|
|
||||||
.sort(function (a, b) { return b.n - a.n; })
|
|
||||||
.slice(0, 5);
|
|
||||||
if (!hits.length) return '';
|
|
||||||
return '<div class="lib-char-rels">'
|
|
||||||
+ hits.map(function (x) {
|
|
||||||
const h = _charHue(x.c.name);
|
|
||||||
return '<span class="lib-char-rel-dot" style="background:hsl(' + h + ',55%,38%)" title="' + escHtml(x.c.name) + ' (' + x.n + '×)">'
|
|
||||||
+ escHtml((x.c.name || '?')[0].toUpperCase()) + '</span>';
|
|
||||||
}).join('')
|
|
||||||
+ '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single shared floating preview element, positioned near whichever avatar
|
// Single shared floating preview element, positioned near whichever avatar
|
||||||
// is currently hovered — created lazily on first use rather than once per
|
// is currently hovered — created lazily on first use rather than once per
|
||||||
// row (there can be dozens of rows in a production).
|
// row (there can be dozens of rows in a production).
|
||||||
@ -1037,24 +1014,11 @@ function _charCardHtml(rec, allChars) {
|
|||||||
: tier === 'supporting' ? '<span class="lib-char-tier support">Neben</span>' : '';
|
: tier === 'supporting' ? '<span class="lib-char-tier support">Neben</span>' : '';
|
||||||
const gender = String(sh.gender || '').toLowerCase();
|
const gender = String(sh.gender || '').toLowerCase();
|
||||||
const genderIcon = gender.startsWith('f') ? 'mdi-gender-female' : gender.startsWith('m') ? 'mdi-gender-male' : 'mdi-gender-non-binary';
|
const genderIcon = gender.startsWith('f') ? 'mdi-gender-female' : gender.startsWith('m') ? 'mdi-gender-male' : 'mdi-gender-non-binary';
|
||||||
const snippet = _libStr(sh.mannerisms || sh.voice_pattern || sh.motivation || sh.backstory || '').slice(0, 190);
|
|
||||||
const roleLine = _libStr(sh.profession || sh.archetype).trim();
|
const roleLine = _libStr(sh.profession || sh.archetype).trim();
|
||||||
const ageLabel = _charAgeLabel(sh);
|
const ageLabel = _charAgeLabel(sh);
|
||||||
const langLabel = _charLangLabel(rec);
|
|
||||||
const genderLabel = _charGenderLabel(sh);
|
const genderLabel = _charGenderLabel(sh);
|
||||||
const bookLabel = _libStr(rec.book || '');
|
const bookLabel = _libStr(rec.book || '');
|
||||||
const lineLabel = sh.line_count != null ? String(sh.line_count) + ' Zeilen' : '';
|
const lineLabel = sh.line_count != null ? String(sh.line_count) + ' Zeilen' : '';
|
||||||
// Every character is auto-tagged with the book it was found in (see
|
|
||||||
// clUpsert/clMergeTags) — surfacing the chips here is how you tell a
|
|
||||||
// character recurring across several books apart from a same-named one
|
|
||||||
// that's book-local, since tags (not the single `book` field) are what
|
|
||||||
// carries multi-book membership.
|
|
||||||
const tagList = String(rec.tags || '').split(',').map(function (t) { return t.trim(); }).filter(Boolean);
|
|
||||||
const tagsHtml = tagList.length
|
|
||||||
? '<div class="lib-char-tags">' + tagList.map(function (t) {
|
|
||||||
return '<span class="cl-tag-chip"><span class="mdi mdi-tag-outline"></span>' + escHtml(t) + '</span>';
|
|
||||||
}).join('') + '</div>'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
// A saved portrait used to be squeezed into a small 96px circle floating
|
// A saved portrait used to be squeezed into a small 96px circle floating
|
||||||
// over the same colour-gradient banner everyone without a photo gets —
|
// over the same colour-gradient banner everyone without a photo gets —
|
||||||
@ -1158,8 +1122,6 @@ function _charsTableHtml(chars, sortMode, sortDir) {
|
|||||||
const tier = String(sh.tier || '').toLowerCase();
|
const tier = String(sh.tier || '').toLowerCase();
|
||||||
const tierBadge = tier === 'main' ? '<span class="lib-char-tier main">Haupt</span>'
|
const tierBadge = tier === 'main' ? '<span class="lib-char-tier main">Haupt</span>'
|
||||||
: tier === 'supporting' ? '<span class="lib-char-tier support">Neben</span>' : '';
|
: tier === 'supporting' ? '<span class="lib-char-tier support">Neben</span>' : '';
|
||||||
const gender = String(sh.gender || '').toLowerCase();
|
|
||||||
const genderIcon = gender.startsWith('f') ? 'mdi-gender-female' : gender.startsWith('m') ? 'mdi-gender-male' : gender ? 'mdi-gender-non-binary' : '';
|
|
||||||
const genderLabel = _charGenderLabel(sh);
|
const genderLabel = _charGenderLabel(sh);
|
||||||
const score = sh.moral_alignment_score;
|
const score = sh.moral_alignment_score;
|
||||||
const pct = score != null ? Math.max(0, Math.min(100, score)) : null;
|
const pct = score != null ? Math.max(0, Math.min(100, score)) : null;
|
||||||
@ -2222,7 +2184,6 @@ window._charDetailPage = _charDetailPage;
|
|||||||
|
|
||||||
function _charDetailModal(rec, allChars) {
|
function _charDetailModal(rec, allChars) {
|
||||||
const sh = rec.sheet || {};
|
const sh = rec.sheet || {};
|
||||||
const cov = libBookCover(rec.name);
|
|
||||||
const hue = _charHue(rec.name);
|
const hue = _charHue(rec.name);
|
||||||
const tier = String(sh.tier || '').toLowerCase();
|
const tier = String(sh.tier || '').toLowerCase();
|
||||||
const tierLabel = tier === 'main' ? 'Hauptcharakter' : tier === 'supporting' ? 'Nebencharakter' : tier === 'minor' ? 'Nebenfigur' : '';
|
const tierLabel = tier === 'main' ? 'Hauptcharakter' : tier === 'supporting' ? 'Nebencharakter' : tier === 'minor' ? 'Nebenfigur' : '';
|
||||||
|
|||||||
@ -851,24 +851,6 @@ function confidenceLabel(c) {
|
|||||||
// says the same thing in context, so it was just a second, redundant
|
// says the same thing in context, so it was just a second, redundant
|
||||||
// warning -- removed.
|
// warning -- removed.
|
||||||
|
|
||||||
// The next page after `afterPage` that still needs a human decision -- no
|
|
||||||
// article covers it at all, or one does but is still sitting at its
|
|
||||||
// detected/unreviewed status. A page touched by several articles only
|
|
||||||
// counts as resolved once none of them are still pending.
|
|
||||||
function magNextUnresolvedPage(afterPage) {
|
|
||||||
const pageCount = state.doc?.pageCount || 0;
|
|
||||||
const byPage = new Map();
|
|
||||||
state.articles.forEach((a) => (a.pages || []).forEach((p) => {
|
|
||||||
if (!byPage.has(p)) byPage.set(p, []);
|
|
||||||
byPage.get(p).push(a);
|
|
||||||
}));
|
|
||||||
for (let p = afterPage + 1; p <= pageCount; p++) {
|
|
||||||
const arts = byPage.get(p) || [];
|
|
||||||
if (!arts.length || arts.some((a) => a.status === 'detected')) return p;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opens Review at a specific page even when no article covers it, by
|
// Opens Review at a specific page even when no article covers it, by
|
||||||
// anchoring on whichever approved-or-not article is nearest that page (the
|
// anchoring on whichever approved-or-not article is nearest that page (the
|
||||||
// review UI needs *an* article as context, but its own page navigation is
|
// review UI needs *an* article as context, but its own page navigation is
|
||||||
@ -4131,8 +4113,6 @@ async function magSetStatus(status) {
|
|||||||
// exactly what is read as one paragraph: below (same column), column (top of another column) or
|
// exactly what is read as one paragraph: below (same column), column (top of another column) or
|
||||||
// page (another page).
|
// page (another page).
|
||||||
|
|
||||||
const MAG_FLOW_LABEL = { below: 'continues below', column: 'continues in the next column', page: 'continues on page' };
|
|
||||||
|
|
||||||
async function magFlowLoad() {
|
async function magFlowLoad() {
|
||||||
const a = state.article;
|
const a = state.article;
|
||||||
if (!a || !state.doc) return;
|
if (!a || !state.doc) return;
|
||||||
|
|||||||
@ -488,7 +488,6 @@ function _lineAudioCachePut(book, key, blob) {
|
|||||||
// of the current script's lines already have a matching file, and lights
|
// of the current script's lines already have a matching file, and lights
|
||||||
// up their dots — without downloading any actual audio (that still only
|
// up their dots — without downloading any actual audio (that still only
|
||||||
// happens lazily, right when a line is about to play).
|
// happens lazily, right when a line is about to play).
|
||||||
let _lineAudioSyncedFor = null; // unused now, kept for compat
|
|
||||||
async function _lineAudioSyncDots() {
|
async function _lineAudioSyncDots() {
|
||||||
const book = _lineAudioBookName();
|
const book = _lineAudioBookName();
|
||||||
_ensureNarrator();
|
_ensureNarrator();
|
||||||
|
|||||||
@ -1350,7 +1350,6 @@ function rehMarkCastDesigning(sp, state, msg, info) {
|
|||||||
} else if (state === 'done' && panel) {
|
} else if (state === 'done' && panel) {
|
||||||
// Collapse to a summary line
|
// Collapse to a summary line
|
||||||
const descEl = panel.querySelector('.reh-cdp-desc');
|
const descEl = panel.querySelector('.reh-cdp-desc');
|
||||||
const metaEl = panel.querySelector('.reh-cdp-meta');
|
|
||||||
const stepEl = panel.querySelector('.reh-cdp-step');
|
const stepEl = panel.querySelector('.reh-cdp-step');
|
||||||
if (stepEl) stepEl.remove();
|
if (stepEl) stepEl.remove();
|
||||||
if (descEl) {
|
if (descEl) {
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
// single-language audiobook.
|
// single-language audiobook.
|
||||||
const SEED_FINDER_TEXT_DE = 'Die dreitausendfünfhundertsiebenundsechzig neuen Geräte für das Update benötigten eine außergewöhnlich starke Kühlung und regelmäßige Wartung, um die Leistung bei großer Last zu gewährleisten. Notiere dir den einundzwanzigsten Juni um vierzehn Uhr. Es ist reine Zeitverschwendung, etwas Mittelmäßiges zu tun! Träume beginnen mit einem positiven Mindset.';
|
const SEED_FINDER_TEXT_DE = 'Die dreitausendfünfhundertsiebenundsechzig neuen Geräte für das Update benötigten eine außergewöhnlich starke Kühlung und regelmäßige Wartung, um die Leistung bei großer Last zu gewährleisten. Notiere dir den einundzwanzigsten Juni um vierzehn Uhr. Es ist reine Zeitverschwendung, etwas Mittelmäßiges zu tun! Träume beginnen mit einem positiven Mindset.';
|
||||||
const SEED_FINDER_TEXT_EN = 'The new devices for the update required exceptionally strong cooling and regular maintenance to guarantee performance under heavy load. Make a note for the twenty-first of June at two in the afternoon. It is a genuine waste of time to do something mediocre! Dreams begin with a positive mindset.';
|
const SEED_FINDER_TEXT_EN = 'The new devices for the update required exceptionally strong cooling and regular maintenance to guarantee performance under heavy load. Make a note for the twenty-first of June at two in the afternoon. It is a genuine waste of time to do something mediocre! Dreams begin with a positive mindset.';
|
||||||
const SEED_FINDER_TEXT_TORTURE = 'Die 3.567 neuen High-End Geräte für das Server-Update benötigen eine außergewöhnlich starke Kühlung und regelmäßige Maßnahmen, um die Performance bei großer Last zu gewährleisten. - The system administrator successfully configured the customized Docker stacks and benchmarked the inference engines at exactly 8:45 AM. - Notiere dir an Midsummer 21.06. um 14 Uhr - Es ist reine Zeitverschwendung, etwas Mittelmäßiges zu tun! Schöne Grüße! — Madonna - Träume beginnt mit einem positiven Mindset.';
|
|
||||||
|
|
||||||
const _sfT = s => (typeof window.t === 'function' ? window.t(s) : s);
|
const _sfT = s => (typeof window.t === 'function' ? window.t(s) : s);
|
||||||
|
|
||||||
|
|||||||
@ -808,7 +808,7 @@ document.addEventListener('click', async e => { if (!e.target.closest('.s-save-b
|
|||||||
['vibevoice', 's-tts-extra-vibevoice', 'VibeVoice'],
|
['vibevoice', 's-tts-extra-vibevoice', 'VibeVoice'],
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
for (const [key, id, label] of paramFields) {
|
for (const [key, id] of paramFields) {
|
||||||
ttsExtraParamsByBackend[key] = JSON.parse($(id).value || '{}');
|
ttsExtraParamsByBackend[key] = JSON.parse($(id).value || '{}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -247,7 +247,7 @@ function _statusApplyToSelects(ids, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function _statusOpenLlmFlyup(anchor) {
|
async function _statusOpenLlmFlyup(anchor) {
|
||||||
const { url, model, apiKey } = statusLlmTarget();
|
const { url, model } = statusLlmTarget();
|
||||||
if (!url) { _statusFlyupList(anchor, [], null, 'No LLM endpoint configured', 'llm'); return; }
|
if (!url) { _statusFlyupList(anchor, [], null, 'No LLM endpoint configured', 'llm'); return; }
|
||||||
_statusFlyupList(anchor, [], null, 'Loading models…', 'llm');
|
_statusFlyupList(anchor, [], null, 'Loading models…', 'llm');
|
||||||
let models = [];
|
let models = [];
|
||||||
@ -315,7 +315,7 @@ document.addEventListener('click', e => {
|
|||||||
|
|
||||||
async function refreshStatusBarEngines({ force = false } = {}) {
|
async function refreshStatusBarEngines({ force = false } = {}) {
|
||||||
updateStatusBar();
|
updateStatusBar();
|
||||||
const { url, model, apiKey } = statusLlmTarget();
|
const { url, model } = statusLlmTarget();
|
||||||
if (!url || STATUS_LLM_CACHE.pending) return;
|
if (!url || STATUS_LLM_CACHE.pending) return;
|
||||||
const fresh = STATUS_LLM_CACHE.url === url && STATUS_LLM_CACHE.model === model && (Date.now() - STATUS_LLM_CACHE.checked) < 30000;
|
const fresh = STATUS_LLM_CACHE.url === url && STATUS_LLM_CACHE.model === model && (Date.now() - STATUS_LLM_CACHE.checked) < 30000;
|
||||||
if (fresh && !force) return;
|
if (fresh && !force) return;
|
||||||
|
|||||||
@ -910,7 +910,6 @@ document.querySelectorAll('.qwen-sample').forEach(card => {
|
|||||||
|
|
||||||
async function runVoiceDesign() {
|
async function runVoiceDesign() {
|
||||||
const baseInstruct=$('design-instruct').value.trim(), sample=$('design-sample-text').value.trim();
|
const baseInstruct=$('design-instruct').value.trim(), sample=$('design-sample-text').value.trim();
|
||||||
const dialogue = isDialogueDesign(baseInstruct, sample, currentDesignSource);
|
|
||||||
const instruct = baseInstruct;
|
const instruct = baseInstruct;
|
||||||
if (!instruct) { toast('Enter a voice description first','error'); return; }
|
if (!instruct) { toast('Enter a voice description first','error'); return; }
|
||||||
$('design-generate-btn').disabled=true; $('design-status').textContent='Generating…';
|
$('design-generate-btn').disabled=true; $('design-status').textContent='Generating…';
|
||||||
|
|||||||
@ -54,7 +54,6 @@ function selectVoice(wrap) {
|
|||||||
const dispName = (v.name && String(v.name).trim())
|
const dispName = (v.name && String(v.name).trim())
|
||||||
? String(v.name).trim()
|
? String(v.name).trim()
|
||||||
: (nameParts.length > 1 ? nameParts[nameParts.length - 1] : voiceId);
|
: (nameParts.length > 1 ? nameParts[nameParts.length - 1] : voiceId);
|
||||||
const initial = (dispName[0] || voiceId[0] || '?').toUpperCase();
|
|
||||||
const picSrcInsp = hasPicture ? `/api/voice/picture/${encodeURIComponent(voiceId)}` : null;
|
const picSrcInsp = hasPicture ? `/api/voice/picture/${encodeURIComponent(voiceId)}` : null;
|
||||||
const inspFlagCc = flagCc || langCode;
|
const inspFlagCc = flagCc || langCode;
|
||||||
const inspFlagIcon = inspFlagCc
|
const inspFlagIcon = inspFlagCc
|
||||||
|
|||||||
@ -279,7 +279,6 @@ const FLAG_LANGUAGE_CANDIDATES = {
|
|||||||
CN:['ZH'], TW:['ZH'], HK:['ZH'], MO:['ZH'], JP:['JA'], KR:['KO'], VN:['VI'], TH:['TH'], ID:['ID'], MY:['MS'], PK:['UR'], BD:['BN'], LK:['SI'], NP:['NE'],
|
CN:['ZH'], TW:['ZH'], HK:['ZH'], MO:['ZH'], JP:['JA'], KR:['KO'], VN:['VI'], TH:['TH'], ID:['ID'], MY:['MS'], PK:['UR'], BD:['BN'], LK:['SI'], NP:['NE'],
|
||||||
SA:['AR'], EG:['AR'], AE:['AR'], MA:['AR'], QA:['AR'], KW:['AR'], OM:['AR'], JO:['AR'], LB:['AR'], IQ:['AR'], IR:['FA'], IL:['HE'],
|
SA:['AR'], EG:['AR'], AE:['AR'], MA:['AR'], QA:['AR'], KW:['AR'], OM:['AR'], JO:['AR'], LB:['AR'], IQ:['AR'], IR:['FA'], IL:['HE'],
|
||||||
};
|
};
|
||||||
const FLAG_LANGUAGE = Object.fromEntries(Object.entries(FLAG_LANGUAGE_CANDIDATES).map(([cc, langs]) => [cc, langs[0]]));
|
|
||||||
|
|
||||||
const LANGUAGE_LABELS = {
|
const LANGUAGE_LABELS = {
|
||||||
EN:'English', DE:'German', FR:'French', ES:'Spanish', PT:'Portuguese', IT:'Italian', NL:'Dutch', PL:'Polish', SV:'Swedish',
|
EN:'English', DE:'German', FR:'French', ES:'Spanish', PT:'Portuguese', IT:'Italian', NL:'Dutch', PL:'Polish', SV:'Swedish',
|
||||||
@ -935,12 +934,6 @@ function setBenchmarkProgress(done, total, label = '', stats = {}) {
|
|||||||
if (last && stats.last) last.textContent = stats.last;
|
if (last && stats.last) last.textContent = stats.last;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideBenchmarkProgress() {
|
|
||||||
$('benchmark-progress').hidden = true;
|
|
||||||
$('benchmark-progress-bar').style.width = '0%';
|
|
||||||
if ($('benchmark-live-last')) $('benchmark-live-last').textContent = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clearTtsRestartFlags() {
|
async function clearTtsRestartFlags() {
|
||||||
const r = await fetch('/api/tts/restart-flags/clear', { method:'POST' });
|
const r = await fetch('/api/tts/restart-flags/clear', { method:'POST' });
|
||||||
if (!r.ok) { const e = await r.json().catch(()=>({})); throw new Error(e.detail || r.statusText); }
|
if (!r.ok) { const e = await r.json().catch(()=>({})); throw new Error(e.detail || r.statusText); }
|
||||||
@ -1878,7 +1871,6 @@ function renderVoiceList() {
|
|||||||
if (window._voiceTagFilter) {
|
if (window._voiceTagFilter) {
|
||||||
filtered = filtered.filter(v => String(v.tag || '').split(',').map(t => t.trim()).includes(window._voiceTagFilter));
|
filtered = filtered.filter(v => String(v.tag || '').split(',').map(t => t.trim()).includes(window._voiceTagFilter));
|
||||||
}
|
}
|
||||||
const visibleCount = filtered.length;
|
|
||||||
filtered = filtered.filter(libraryFilterMatch);
|
filtered = filtered.filter(libraryFilterMatch);
|
||||||
const filterCount = filtered.length;
|
const filterCount = filtered.length;
|
||||||
if (_libraryIssueFilter) filtered = filtered.filter(v => libraryIssueMatch(v));
|
if (_libraryIssueFilter) filtered = filtered.filter(v => libraryIssueMatch(v));
|
||||||
@ -2257,10 +2249,6 @@ function makeVoiceRow(v) {
|
|||||||
// confusing 404 for a voice that clearly has reference audio in its own
|
// confusing 404 for a voice that clearly has reference audio in its own
|
||||||
// panel (confirmed live).
|
// panel (confirmed live).
|
||||||
const isClone = !!v.has_ref;
|
const isClone = !!v.has_ref;
|
||||||
const initial = v.id[0] ? v.id[0].toUpperCase() : '?';
|
|
||||||
const flagIconHtml = currentFlag
|
|
||||||
? `<span class="fi fi-${currentFlag.toLowerCase()}" role="img" aria-label="${langCode}"></span>`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Avatar: photo > gender/type icon > silhouette (with color)
|
// Avatar: photo > gender/type icon > silhouette (with color)
|
||||||
const defaultSilClass = gender === 'M' ? 'mdi-face-man' : (gender === 'F' ? 'mdi-face-woman' : 'mdi-account');
|
const defaultSilClass = gender === 'M' ? 'mdi-face-man' : (gender === 'F' ? 'mdi-face-woman' : 'mdi-account');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user