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

42
db.js
View file

@ -30,6 +30,21 @@ db.exec(`
updated_at INTEGER NOT NULL,
PRIMARY KEY (streamer_id, google_id)
);
CREATE TABLE IF NOT EXISTS viewer_visits (
streamer_id INTEGER NOT NULL,
google_id TEXT NOT NULL,
first_seen_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL,
PRIMARY KEY (streamer_id, google_id)
);
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
streamer_id INTEGER NOT NULL,
google_id TEXT NOT NULL,
created_at INTEGER NOT NULL
);
`);
// Idempotent migration for databases created before display_duration_seconds existed.
@ -94,6 +109,30 @@ function updateAiName(streamerId, aiName) {
db.prepare('UPDATE streamers SET ai_name = ? WHERE id = ?').run(aiName, streamerId);
}
function recordVisit(streamerId, googleId) {
const now = Date.now();
db.prepare(
`INSERT INTO viewer_visits (streamer_id, google_id, first_seen_at, last_seen_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(streamer_id, google_id) DO UPDATE SET last_seen_at = excluded.last_seen_at`
).run(streamerId, googleId, now, now);
}
function recordMessage(streamerId, googleId) {
db.prepare(
'INSERT INTO messages (streamer_id, google_id, created_at) VALUES (?, ?, ?)'
).run(streamerId, googleId, Date.now());
}
function getStreamerStats(streamerId) {
const visitors = db.prepare('SELECT COUNT(*) AS c FROM viewer_visits WHERE streamer_id = ?').get(streamerId).c;
const messages = db.prepare('SELECT COUNT(*) AS c FROM messages WHERE streamer_id = ?').get(streamerId).c;
const uniqueSenders = db
.prepare('SELECT COUNT(DISTINCT google_id) AS c FROM messages WHERE streamer_id = ?')
.get(streamerId).c;
return { visitors, messages, uniqueSenders };
}
module.exports = {
findStreamerByOverlayToken,
findStreamerBySenderToken,
@ -102,4 +141,7 @@ module.exports = {
setViewerName,
updateDisplayDuration,
updateAiName,
recordVisit,
recordMessage,
getStreamerStats,
};