feat: implement word-by-word streaming typing effect

This commit is contained in:
admin
2026-05-05 13:38:31 +00:00
Unverified
parent 6dd1de590b
commit 0d96c4fb7f

View File

@@ -45,36 +45,48 @@ export async function sendFormatted(ctx, text) {
export async function sendStreamingMessage(ctx, text, options = {}) { export async function sendStreamingMessage(ctx, text, options = {}) {
if (!text) return; 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 { try {
// Stream chunks with typing indicator // Send initial "typing..." indicator
const chunks = splitMessage(text); const sendTyping = () => ctx.api.sendChatAction(ctx.chat.id, 'typing').catch(() => {});
let delayTimer = null;
for (const chunk of chunks) { if (charMode) {
// Clear previous typing indicator // Character-by-character streaming
if (delayTimer) { let sentText = '';
clearTimeout(delayTimer); const chars = text.split('');
delayTimer = null;
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));
} }
} else {
// Word-by-word streaming (default)
const words = text.split(' ');
let sentText = '';
// Send chunk with minimal delay for real-time effect for (let i = 0; i < words.length; i++) {
await ctx.reply(chunk, { parse_mode: 'Markdown' }); sentText += (i > 0 ? ' ' : '') + words[i];
// Show typing indicator between chunks // Send accumulated text
if (chunks.length > 1) { await ctx.reply(sentText, { parse_mode: 'Markdown' });
delayTimer = setTimeout(() => {
ctx.api.sendChatAction(ctx.chat.id, 'typing').catch(() => {}); // Variable delay based on word length
}, delay); 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) { } catch (error) {
logger.error('Streaming send failed:', error); logger.error('Streaming send failed:', error);
// Fallback to non-streaming // Fallback to non-streaming