feat: контекст чата — бот помнит последние N сообщений
Бот подхватывает последние 20 (настраивается) сообщений из чата как контекст при ответе. Команда /context для просмотра и настройки. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
81
src/index.ts
81
src/index.ts
@@ -87,6 +87,45 @@ const pendingQuestions = new Map<string, {
|
|||||||
// Track the last session that sent a message (for auto-routing replies)
|
// Track the last session that sent a message (for auto-routing replies)
|
||||||
let lastMessageSession: string | null = null;
|
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
|
// Clean up expired sessions periodically
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -167,7 +206,9 @@ bot.command('help', async (ctx) => {
|
|||||||
'*Chatbot:*\n' +
|
'*Chatbot:*\n' +
|
||||||
'• @mention или reply — Claude ответит как чат-бот\n' +
|
'• @mention или reply — Claude ответит как чат-бот\n' +
|
||||||
'`/chatbot` - Статус чат-бота\n' +
|
'`/chatbot` - Статус чат-бота\n' +
|
||||||
'`/chatreset` - Сброс диалога\n\n' +
|
'`/chatreset` - Сброс диалога\n' +
|
||||||
|
'`/context` - Сколько сообщений бот помнит из чата\n' +
|
||||||
|
'`/context N` - Установить кол-во (0 = выкл)\n\n' +
|
||||||
'*Background Tasks:*\n' +
|
'*Background Tasks:*\n' +
|
||||||
'`/task <message>` - Создать фоновую задачу (без таймаута)\n' +
|
'`/task <message>` - Создать фоновую задачу (без таймаута)\n' +
|
||||||
'`/task list` - Список задач\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) => {
|
bot.command('task', async (ctx) => {
|
||||||
const text = ctx.message.text.replace(/^\/task\s*/, '').trim();
|
const text = ctx.message.text.replace(/^\/task\s*/, '').trim();
|
||||||
const userChatId = ctx.chat.id.toString();
|
const userChatId = ctx.chat.id.toString();
|
||||||
@@ -537,6 +607,9 @@ bot.on('text', async (ctx) => {
|
|||||||
|
|
||||||
console.log(`\n📨 Message from ${from}: "${message}"\n`);
|
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
|
// Check if this is an answer to a pending question
|
||||||
const questionId = Array.from(pendingQuestions.keys())[0];
|
const questionId = Array.from(pendingQuestions.keys())[0];
|
||||||
if (questionId && pendingQuestions.has(questionId)) {
|
if (questionId && pendingQuestions.has(questionId)) {
|
||||||
@@ -725,10 +798,14 @@ bot.on('text', async (ctx) => {
|
|||||||
const userChatId = ctx.chat.id.toString();
|
const userChatId = ctx.chat.id.toString();
|
||||||
console.log(`🤖 Chatbot handling message from ${from}: "${cleanMessage.substring(0, 50)}..."`);
|
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
|
// Fire-and-forget: don't block Telegraf's handler
|
||||||
handleChatbotMessage(
|
handleChatbotMessage(
|
||||||
userChatId,
|
userChatId,
|
||||||
cleanMessage,
|
messageWithContext,
|
||||||
async () => {
|
async () => {
|
||||||
await ctx.sendChatAction('typing');
|
await ctx.sendChatAction('typing');
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user