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

@ -17,6 +17,9 @@ const {
setViewerName,
updateDisplayDuration,
updateAiName,
recordVisit,
recordMessage,
getStreamerStats,
} = require('./db');
const { censorText } = require('./profanity');
@ -127,6 +130,7 @@ app.get('/api/dashboard', requireAuth, (req, res) => {
ttsVoice: streamer.tts_voice,
displayDurationSeconds: streamer.display_duration_seconds,
aiName: streamer.ai_name,
stats: getStreamerStats(streamer.id),
});
});
@ -265,6 +269,14 @@ io.on('connection', (socket) => {
} else {
socket.join(`streamer-${streamer.id}`);
}
} else if (role === 'sender' && connectedUser) {
// Only reached on the reconnect that happens right after login (see
// socket.disconnect()/connect() in send.js) — the initial anonymous
// connection has no session.user yet, so no visit is recorded for it.
const streamer = findStreamerBySenderToken(token);
if (streamer) {
recordVisit(streamer.id, connectedUser.id);
}
}
socket.on('disconnect', (reason) => {
@ -294,6 +306,8 @@ io.on('connection', (socket) => {
return;
}
recordMessage(streamer.id, user.id);
const rawMessage = text.trim().slice(0, 500);
const message = censorText(rawMessage, '...');
const speechMessage = censorText(rawMessage, 'бип');