Fix the speak status tracker and null option handling

This commit is contained in:
Fisto
2026-09-21 18:00:41 +02:00
parent 7701664bc6
commit 15901849a1
3 changed files with 64 additions and 17 deletions
+40
View File
@@ -1715,6 +1715,45 @@ function fillDeviceSelect(select, devices, defaultLabel) {
if ([...select.options].some(option => option.value === current)) select.value = current;
}
// The daemon plays asynchronously; keep the panel honest about queued vs speaking vs done.
let speechTracker = 0;
async function trackSpeech() {
const token = ++speechTracker;
for (let attempt = 0; attempt < 1200; attempt++) {
await new Promise(resolve => setTimeout(resolve, 500));
if (token !== speechTracker) return;
try {
const status = await VK.api.get("/api/audio");
if (token !== speechTracker) return;
const label = $("#tts-status");
if (status?.playing === true) {
label.textContent = "speaking\u2026";
continue;
}
const queue = Number(status?.queue ?? 0);
if (queue > 0) {
label.textContent = `queued (${queue})`;
continue;
}
label.textContent = "done";
setTimeout(() => {
if (token === speechTracker && $("#tts-status").textContent === "done") {
$("#tts-status").textContent = "idle";
}
}, 4000);
return;
} catch {
return;
}
}
}
async function refreshAudio() {
try {
const status = await VK.api.get("/api/audio");
@@ -2344,6 +2383,7 @@ async function speakText() {
$("#tts-status").textContent = "queued";
toast("Queued for speech", "ok");
void refreshAudio();
void trackSpeech();
} catch (error) {
toast(error.message, "error");
}
+17 -17
View File
@@ -754,17 +754,21 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
? requested
: prefs.tts.provider;
const voice = req.body?.voice ? String(req.body.voice) : prefs.tts.voice ?? undefined;
const speed = req.body?.speed !== undefined ? Number(req.body.speed) : prefs.tts.speed ?? undefined;
const pitch = req.body?.pitch !== undefined ? Number(req.body.pitch) : prefs.tts.pitch ?? undefined;
const expression =
req.body?.expression !== undefined ? Number(req.body.expression) : prefs.tts.expression ?? undefined;
// Number(null) is 0: null, undefined, and "" all mean "unset".
const optional = (value: unknown): number | undefined => {
if (value === undefined || value === null || value === "") return undefined;
const number = Number(value);
return Number.isFinite(number) ? number : undefined;
};
const speed = optional(req.body?.speed) ?? prefs.tts.speed ?? undefined;
const pitch = optional(req.body?.pitch) ?? prefs.tts.pitch ?? undefined;
const expression = optional(req.body?.expression) ?? prefs.tts.expression ?? undefined;
const profile = req.body?.profile && typeof req.body.profile === "object" ? req.body.profile : prefs.tts.profile;
const highpass =
req.body?.highpass !== undefined ? Number(req.body.highpass) : prefs.tts.highpass ?? undefined;
const echo = req.body?.echo !== undefined ? Number(req.body.echo) : prefs.tts.echo ?? undefined;
const highpass = optional(req.body?.highpass) ?? prefs.tts.highpass ?? undefined;
const echo = optional(req.body?.echo) ?? prefs.tts.echo ?? undefined;
const words = req.body?.words !== undefined ? req.body.words === true : prefs.tts.words;
const wordGapMs =
req.body?.wordGapMs !== undefined ? Number(req.body.wordGapMs) : prefs.tts.wordGapMs ?? undefined;
const wordGapMs = optional(req.body?.wordGapMs) ?? prefs.tts.wordGapMs ?? undefined;
// The Half-Life VOX voice carries its prosody with it.
const vox =
(req.body?.vox !== undefined ? req.body.vox === true : prefs.tts.vox) || voice === "vox";
@@ -775,20 +779,16 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
: req.body?.preset === null || req.body?.preset === ""
? null
: prefs.tts.preset;
const echoDelayMs =
req.body?.echoDelayMs !== undefined ? Number(req.body.echoDelayMs) : prefs.tts.echoDelayMs ?? undefined;
const echoLevel =
req.body?.echoLevel !== undefined ? Number(req.body.echoLevel) : prefs.tts.echoLevel ?? undefined;
const echoDelayMs = optional(req.body?.echoDelayMs) ?? prefs.tts.echoDelayMs ?? undefined;
const echoLevel = optional(req.body?.echoLevel) ?? prefs.tts.echoLevel ?? undefined;
const crunch =
["hl90", "hl90hq", "telephone", "adpcm", "crush"].includes(String(req.body?.crunch ?? ""))
? (String(req.body.crunch) as "hl90" | "hl90hq" | "telephone" | "adpcm" | "crush")
: req.body?.crunch === null || req.body?.crunch === "" || req.body?.crunch === "off"
? undefined
: prefs.tts.crunch ?? undefined;
const crunchBits =
req.body?.crunchBits !== undefined ? Number(req.body.crunchBits) : prefs.tts.crunchBits ?? undefined;
const crunchRate =
req.body?.crunchRate !== undefined ? Number(req.body.crunchRate) : prefs.tts.crunchRate ?? undefined;
const crunchBits = optional(req.body?.crunchBits) ?? prefs.tts.crunchBits ?? undefined;
const crunchRate = optional(req.body?.crunchRate) ?? prefs.tts.crunchRate ?? undefined;
const streamWords =
req.body?.streamWords !== undefined ? req.body.streamWords !== false : prefs.tts.streamWords;