Add streamer dashboard stats; fix TTS overlap race properly
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 30s

Dashboard now shows unique visitors (via sender link), total messages,
and unique senders per streamer, tracked via new viewer_visits/messages
tables.

The previous overlay TTS-overlap fix had a race: audio not having
arrived yet was treated the same as "no audio at all", letting the
queue advance before slow-to-synthesize audio (e.g. a long question)
ever started playing. Now "not arrived yet" is explicitly distinct
from "finished playing", with a grace-period fallback for genuine TTS
failures.

Also drop the hardcoded AI name from ai-prompt.txt since it's now
injected dynamically from the streamer's configured ai_name.
This commit is contained in:
vrubelroman 2026-07-08 18:04:02 +00:00
parent ec6d8c4cfe
commit b669c2d0b7
5 changed files with 114 additions and 9 deletions

View file

@ -70,24 +70,28 @@ function advanceQueue() {
playNotificationSound();
// The message stays up for at least durationMs (streamer's configured
// display time), but if its TTS audio is still playing when that timer
// fires (e.g. a long question read aloud), we must not start the next
// message's audio on top of it — so we also wait for the audio to
// actually finish before advancing.
// display time), but if its TTS audio is still playing (or hasn't even
// arrived yet — synthesis is async and can take a few seconds, longer
// than a short configured duration) when that timer fires, we must not
// start the next message's audio on top of it. So we only advance once
// BOTH the minimum time has passed AND we know the audio is actually
// done — "not arrived yet" is treated as "still waiting", not "done",
// unlike an earlier version of this code that got that backwards.
let minTimeElapsed = false;
let audioFinished = !current.audio;
// 'unknown' (no audio yet, may still arrive), 'playing', or 'done'.
let audioState = current.audio ? 'playing' : 'unknown';
const maybeAdvance = () => {
if (!minTimeElapsed || !audioFinished) return;
if (!minTimeElapsed || audioState !== 'done') return;
box.classList.remove('visible');
current = null;
setTimeout(advanceQueue, FADE_MS);
};
current.startAudio = (audio) => {
audioFinished = false;
audioState = 'playing';
playSpeech(audio.data, audio.mimeType, () => {
audioFinished = true;
audioState = 'done';
maybeAdvance();
});
};
@ -100,6 +104,15 @@ function advanceQueue() {
minTimeElapsed = true;
maybeAdvance();
}, current.durationMs || 10000);
// Safety net: if TTS synthesis fails server-side, no display_audio ever
// arrives for this id — don't wait forever for audio that's never coming.
setTimeout(() => {
if (audioState === 'unknown') {
audioState = 'done';
maybeAdvance();
}
}, (current.durationMs || 10000) + 8000);
}
socket.on('display_message', (payload) => {