Replay now matches the robots and no longer cuts words:
- read the turn to turnComplete, not generationComplete, and drain the
socket before each send; breaking early truncated every sentence and
left frames that the next turn mis-read as its own reply
- accept a take only if the model's own transcript covers the text AND
the audio is long enough to contain it (the transcript reports the
full text even for a 0.8s clip)
- pitch gate: reject an off-tone take and re-ask, per voice, using a
pure-Python F0 estimator (no numpy on the host)
- continuation: speak the words a voice skipped instead of retrying a
line it stops on deterministically
- fresh Live session per replay; delivery drifts as turns accumulate
Live Gemini tab: browser talks to Gemini directly (the reverse proxy
cannot upgrade a WebSocket), with a persona library - named personas,
per-robot selection, built-ins that cannot be overwritten.
Dashboard: records search + voice filter, log panel falls back to
polling, sign-in history with CSV/JSON export, and JS errors now show
on the page instead of silently blanking a tab.