feat(bot): delay login until MeloTTS voice is warm
The bot, bridge and melo worker boot together, but the MeloTTS model takes
tens of seconds to load. If the bot logged in and auto-joined the voice channel
before the voice was warm, the first reply synthesised to nothing and was
silently dropped.
- bridge /health now reports `tts_ready`. For MeloTTS this pings the worker,
which only binds its HTTP port AFTER the model is loaded (main() warms before
serve_forever()), so a successful ping is a precise "voice is warm" signal.
- The bot polls /health and waits for `tts_ready` before logging in. It does
not wait on brain_ready (the reply engine / Whisper load lazily on the first
turn — a slow first turn is fine, a silent one is the bug). After a 180s cap
it proceeds anyway so a TTS load failure degrades to text-only.
Live-verified: startup logs show "⏳ MeloTTS 준비 대기 중" then
"✓ 음성(MeloTTS) 준비 완료 — 로그인 진행" then "✓ 유저봇 로그인", in that order.
This commit is contained in:
@@ -184,10 +184,43 @@ async function handleStatus(i: ChatInputCommandInteraction) {
|
||||
);
|
||||
}
|
||||
|
||||
// Block until the brain bridge reports the TTS voice (MeloTTS worker) is warm.
|
||||
// The bot, bridge and melo worker all boot together; the voice model takes tens
|
||||
// of seconds to load. If we log in and auto-join the voice channel before
|
||||
// MeloTTS is ready, the first reply synthesises to nothing and is silently
|
||||
// dropped. Gating login on TTS readiness guarantees the first spoken turn has
|
||||
// audio. We deliberately do NOT wait on brain_ready: the reply engine / Whisper
|
||||
// load lazily on the first turn (so brain_ready stays false on an idle boot) —
|
||||
// a slow first turn is fine, a silent one is the bug. After `maxMs` we proceed
|
||||
// anyway so a TTS load failure degrades to text-only rather than taking the
|
||||
// whole bot offline.
|
||||
async function waitForBridgeReady(maxMs = 180_000): Promise<void> {
|
||||
const start = Date.now();
|
||||
let announced = false;
|
||||
while (Date.now() - start < maxMs) {
|
||||
try {
|
||||
const h = await health();
|
||||
if (h.tts_ready) {
|
||||
console.log("✓ 음성(MeloTTS) 준비 완료 — 로그인 진행");
|
||||
return;
|
||||
}
|
||||
if (!announced) {
|
||||
console.log("⏳ MeloTTS 준비 대기 중 (음성 모델 로딩)...");
|
||||
announced = true;
|
||||
}
|
||||
} catch {
|
||||
/* bridge not listening yet — keep polling */
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
}
|
||||
console.warn("⚠️ MeloTTS 준비 시간 초과 — 음성(TTS) 없이 진행합니다.");
|
||||
}
|
||||
|
||||
// Mode select: a USER account is the only kind Discord lets Go Live, so when
|
||||
// there's no normal-bot token but a selfbot token is present, run as a userbot
|
||||
// (voice + broadcast on one user account). Otherwise run the legacy normal bot.
|
||||
(async () => {
|
||||
await waitForBridgeReady();
|
||||
if (!config.botToken && config.selfbotToken) {
|
||||
const { runUserbot } = await import("./userbot.ts");
|
||||
await runUserbot();
|
||||
|
||||
Reference in New Issue
Block a user