feat: контекст чата — бот помнит последние N сообщений

Бот подхватывает последние 20 (настраивается) сообщений из чата
как контекст при ответе. Команда /context для просмотра и настройки.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-02-19 17:11:26 +00:00
parent b3fdd383ac
commit df1bbdcc8b

View File

@@ -87,6 +87,45 @@ const pendingQuestions = new Map<string, {
// Track the last session that sent a message (for auto-routing replies)
let lastMessageSession: string | null = null;
// Chat history buffer for chatbot context
const CHATBOT_CONTEXT_MESSAGES = () => parseInt(process.env.CHATBOT_CONTEXT_MESSAGES || '20');
interface ChatHistoryEntry {
from: string;
text: string;
timestamp: number;
}
const chatHistory = new Map<string, ChatHistoryEntry[]>();
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 <message>` - Создать фоновую задачу (без таймаута)\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');
},