From 0d96c4fb7f82fb02879437869673d8baf619503d Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 5 May 2026 13:38:31 +0000 Subject: [PATCH] feat: implement word-by-word streaming typing effect --- src/bot/message-sender.js | 62 +++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/bot/message-sender.js b/src/bot/message-sender.js index 2280ccff..92260c68 100644 --- a/src/bot/message-sender.js +++ b/src/bot/message-sender.js @@ -45,36 +45,48 @@ export async function sendFormatted(ctx, text) { export async function sendStreamingMessage(ctx, text, options = {}) { if (!text) return; - const { delay = 50, maxChunkSize = 1000 } = options; + const { + delay = 100, + minDelay = 50, + maxDelay = 250, + charMode = false // Set to true for character-by-character, false for word-by-word + } = options; try { - // Stream chunks with typing indicator - const chunks = splitMessage(text); - let delayTimer = null; - - for (const chunk of chunks) { - // Clear previous typing indicator - if (delayTimer) { - clearTimeout(delayTimer); - delayTimer = null; + // Send initial "typing..." indicator + const sendTyping = () => ctx.api.sendChatAction(ctx.chat.id, 'typing').catch(() => {}); + + if (charMode) { + // Character-by-character streaming + let sentText = ''; + const chars = text.split(''); + + for (const char of chars) { + sentText += char; + + // Send message with the accumulated text + await ctx.reply(sentText, { parse_mode: 'Markdown' }); + + // Random delay between min and max + const delayMs = minDelay + Math.random() * (maxDelay - minDelay); + await new Promise(resolve => setTimeout(resolve, delayMs)); } - - // Send chunk with minimal delay for real-time effect - await ctx.reply(chunk, { parse_mode: 'Markdown' }); - - // Show typing indicator between chunks - if (chunks.length > 1) { - delayTimer = setTimeout(() => { - ctx.api.sendChatAction(ctx.chat.id, 'typing').catch(() => {}); - }, delay); + } else { + // Word-by-word streaming (default) + const words = text.split(' '); + let sentText = ''; + + for (let i = 0; i < words.length; i++) { + sentText += (i > 0 ? ' ' : '') + words[i]; + + // Send accumulated text + await ctx.reply(sentText, { parse_mode: 'Markdown' }); + + // Variable delay based on word length + const wordDelay = Math.max(minDelay, Math.min(maxDelay, delay + words[i].length * 10)); + await new Promise(resolve => setTimeout(resolve, wordDelay)); } } - - // Clear typing indicator - if (delayTimer) { - clearTimeout(delayTimer); - delayTimer = null; - } } catch (error) { logger.error('Streaming send failed:', error); // Fallback to non-streaming