Hold viewer message until AI reply is ready, when requested
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 30s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 30s
Previously the viewer's message displayed immediately and the AI reply showed up whenever DeepSeek happened to respond, landing with an awkward gap. Now, when the AI checkbox is checked, both texts are fetched/prepared before anything is broadcast, so the original message and the AI reply queue back-to-back on the overlay. Falls back to just showing the original message if the AI call fails. Behavior without the checkbox is unchanged (still immediate). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ad78436ae2
commit
729f6eb47e
2 changed files with 39 additions and 42 deletions
|
|
@ -3,3 +3,4 @@
|
|||
твой ответ будет прочитан вслух на стриме и показан на экране, поэтому не
|
||||
пиши длинных текстов, списков или markdown-разметки.
|
||||
При этом старайся шутить.
|
||||
Не допускать матных слов.
|
||||
|
|
|
|||
78
server.js
78
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue