diff --git a/src/index.ts b/src/index.ts index 3a3d727..25f193f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,6 +87,45 @@ const pendingQuestions = new Map parseInt(process.env.CHATBOT_CONTEXT_MESSAGES || '20'); + +interface ChatHistoryEntry { + from: string; + text: string; + timestamp: number; +} + +const chatHistory = new Map(); + +function addToChatHistory(chatIdKey: string, from: string, text: string): void { + if (!chatHistory.has(chatIdKey)) { + chatHistory.set(chatIdKey, []); + } + const history = chatHistory.get(chatIdKey)!; + history.push({ from, text, timestamp: Date.now() }); + // Keep buffer bounded (2x limit to avoid frequent trimming) + const maxSize = CHATBOT_CONTEXT_MESSAGES() * 2; + if (history.length > maxSize) { + history.splice(0, history.length - maxSize); + } +} + +function getChatContext(chatIdKey: string, excludeLatest: boolean = true): string { + const limit = CHATBOT_CONTEXT_MESSAGES(); + if (limit <= 0) return ''; + + const history = chatHistory.get(chatIdKey); + if (!history || history.length === 0) return ''; + + const entries = excludeLatest ? history.slice(0, -1) : history; + const recent = entries.slice(-limit); + if (recent.length === 0) return ''; + + const lines = recent.map(e => `[${e.from}]: ${e.text}`); + return `Контекст последних ${recent.length} сообщений в чате:\n${lines.join('\n')}\n\n---\nТекущий запрос:\n`; +} + // Clean up expired sessions periodically setInterval(() => { const now = Date.now(); @@ -167,7 +206,9 @@ bot.command('help', async (ctx) => { '*Chatbot:*\n' + '• @mention или reply — Claude ответит как чат-бот\n' + '`/chatbot` - Статус чат-бота\n' + - '`/chatreset` - Сброс диалога\n\n' + + '`/chatreset` - Сброс диалога\n' + + '`/context` - Сколько сообщений бот помнит из чата\n' + + '`/context N` - Установить кол-во (0 = выкл)\n\n' + '*Background Tasks:*\n' + '`/task ` - Создать фоновую задачу (без таймаута)\n' + '`/task list` - Список задач\n' + @@ -225,6 +266,35 @@ bot.command('chatreset', async (ctx) => { } }); +bot.command('context', async (ctx) => { + const arg = ctx.message.text.split(' ').slice(1)[0]; + + if (arg !== undefined) { + const num = parseInt(arg); + if (isNaN(num) || num < 0 || num > 100) { + await ctx.reply('❌ Укажите число от 0 до 100. Пример: `/context 20`', { parse_mode: 'Markdown' }); + return; + } + process.env.CHATBOT_CONTEXT_MESSAGES = num.toString(); + if (num === 0) { + await ctx.reply('🔕 Контекст чата отключён. Бот не будет видеть предыдущие сообщения.'); + } else { + await ctx.reply(`✅ Бот будет помнить последние *${num}* сообщений из чата.`, { parse_mode: 'Markdown' }); + } + return; + } + + const current = CHATBOT_CONTEXT_MESSAGES(); + const historySize = chatHistory.get(ctx.chat.id.toString())?.length || 0; + await ctx.reply( + `*Контекст чата*\n\n` + + `📝 Помнит: *${current}* сообщений\n` + + `💬 В буфере: ${historySize} сообщений\n\n` + + `Изменить: \`/context N\` (0 = выкл)`, + { parse_mode: 'Markdown' } + ); +}); + bot.command('task', async (ctx) => { const text = ctx.message.text.replace(/^\/task\s*/, '').trim(); const userChatId = ctx.chat.id.toString(); @@ -537,6 +607,9 @@ bot.on('text', async (ctx) => { console.log(`\n📨 Message from ${from}: "${message}"\n`); + // Record message in chat history buffer (for chatbot context) + addToChatHistory(ctx.chat.id.toString(), from, message); + // Check if this is an answer to a pending question const questionId = Array.from(pendingQuestions.keys())[0]; if (questionId && pendingQuestions.has(questionId)) { @@ -725,10 +798,14 @@ bot.on('text', async (ctx) => { const userChatId = ctx.chat.id.toString(); console.log(`🤖 Chatbot handling message from ${from}: "${cleanMessage.substring(0, 50)}..."`); + // Prepend chat history as context + const context = getChatContext(userChatId); + const messageWithContext = context ? `${context}${cleanMessage}` : cleanMessage; + // Fire-and-forget: don't block Telegraf's handler handleChatbotMessage( userChatId, - cleanMessage, + messageWithContext, async () => { await ctx.sendChatAction('typing'); },