diff --git a/ai-prompt.txt b/ai-prompt.txt index 670d42f..7cf7056 100644 --- a/ai-prompt.txt +++ b/ai-prompt.txt @@ -2,4 +2,5 @@ адресованное стримеру. Отвечай коротко (1-3 предложения), живо и по делу — твой ответ будет прочитан вслух на стриме и показан на экране, поэтому не пиши длинных текстов, списков или markdown-разметки. -При этом старайся шутить. +При этом старайся шутить. +Не допускать матных слов. diff --git a/server.js b/server.js index f62837e..3654318 100644 --- a/server.js +++ b/server.js @@ -182,6 +182,22 @@ function synthesizeSpeech(text, voice) { }); } +function broadcastMessage(room, id, text, from, durationMs, ttsText, voice) { + io.to(room).emit('display_message', { id, text, from, at: Date.now(), durationMs }); + + synthesizeSpeech(ttsText, voice) + .then((audioBuffer) => { + io.to(room).emit('display_audio', { + id, + audio: audioBuffer.toString('base64'), + mimeType: 'audio/mpeg', + }); + }) + .catch((err) => { + console.error('[tts] synthesis failed:', err); + }); +} + function getAiSystemPrompt() { try { const content = fs.readFileSync(AI_PROMPT_PATH, 'utf8').trim(); @@ -264,59 +280,39 @@ io.on('connection', (socket) => { const messageId = crypto.randomUUID(); const room = `streamer-${streamer.id}`; + const durationMs = (streamer.display_duration_seconds || 10) * 1000; console.log(`[broadcast] streamer=${streamer.id} id=${messageId} from="${displayName}" "${message}"`); - io.to(room).emit('display_message', { - id: messageId, - text: message, - from: displayName, - at: Date.now(), - durationMs: (streamer.display_duration_seconds || 10) * 1000, - }); - // Voice is generated after the fact so the text shows up immediately - // instead of waiting on the round trip to the TTS service. The overlay - // queues messages and matches this to the right one by id, since it may - // arrive before that message's turn to display. - synthesizeSpeech(`Сообщение от ${displayName}. ${message}`, streamer.tts_voice || DEFAULT_TTS_VOICE) - .then((audioBuffer) => { - io.to(room).emit('display_audio', { - id: messageId, - audio: audioBuffer.toString('base64'), - mimeType: 'audio/mpeg', - }); - }) - .catch((err) => { - console.error('[tts] synthesis failed:', err); - }); + const broadcastOriginal = () => { + broadcastMessage( + room, messageId, message, displayName, durationMs, + `Сообщение от ${displayName}. ${message}`, + streamer.tts_voice || DEFAULT_TTS_VOICE + ); + }; if (aiReply && DEEPSEEK_API_KEY) { + // Wait for the AI reply text before showing anything, so the viewer's + // message and the AI reply land in the overlay queue back-to-back + // instead of the reply trailing in later with an awkward gap. getAiReply(message) .then((replyText) => { + broadcastOriginal(); + const aiMessageId = crypto.randomUUID(); console.log(`[ai] streamer=${streamer.id} id=${aiMessageId} reply="${replyText}"`); - io.to(room).emit('display_message', { - id: aiMessageId, - text: replyText, - from: '🤖 ИИ', - at: Date.now(), - durationMs: (streamer.display_duration_seconds || 10) * 1000, - }); - - synthesizeSpeech(`Ответ от ИИ. ${replyText}`, AI_VOICE) - .then((audioBuffer) => { - io.to(room).emit('display_audio', { - id: aiMessageId, - audio: audioBuffer.toString('base64'), - mimeType: 'audio/mpeg', - }); - }) - .catch((err) => { - console.error('[tts] AI reply synthesis failed:', err); - }); + broadcastMessage( + room, aiMessageId, replyText, '🤖 ИИ', durationMs, + `Ответ от ИИ. ${replyText}`, + AI_VOICE + ); }) .catch((err) => { console.error('[ai] DeepSeek reply failed:', err); + broadcastOriginal(); }); + } else { + broadcastOriginal(); } }); });