feat: make DAVE Telegram chat conversational
This commit is contained in:
@@ -111,6 +111,25 @@ export class TelegramAlerter {
|
||||
}
|
||||
}
|
||||
|
||||
async sendConversation(message, opts = {}) {
|
||||
const messages = Array.isArray(message)
|
||||
? message.flatMap(item => this._conversationChunks(item, opts.maxChunkChars))
|
||||
: this._conversationChunks(message, opts.maxChunkChars);
|
||||
if (!messages.length) return { ok: false };
|
||||
let last = { ok: false };
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
last = await this.sendMessage(messages[i], {
|
||||
...opts,
|
||||
parseMode: null,
|
||||
replyToMessageId: i === 0 ? opts.replyToMessageId : undefined,
|
||||
replyMarkup: i === messages.length - 1 ? opts.replyMarkup : undefined,
|
||||
});
|
||||
if (!last.ok) return last;
|
||||
if (i < messages.length - 1) await sleep(opts.delayMs ?? 350);
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split text into chunks of at most maxLen. Prefer breaking at newlines to avoid
|
||||
* splitting mid-Markdown.
|
||||
@@ -131,6 +150,39 @@ export class TelegramAlerter {
|
||||
return chunks;
|
||||
}
|
||||
|
||||
_conversationChunks(text, maxLen = 900) {
|
||||
const cleaned = stripVisibleMarkdown(String(text || '').trim());
|
||||
if (!cleaned) return [];
|
||||
const limit = Math.max(300, Math.min(1800, Number(maxLen) || 900));
|
||||
const paragraphs = cleaned.split(/\n{2,}/).map(part => part.trim()).filter(Boolean);
|
||||
const chunks = [];
|
||||
let current = '';
|
||||
const flush = () => {
|
||||
if (current.trim()) chunks.push(current.trim());
|
||||
current = '';
|
||||
};
|
||||
|
||||
for (const paragraph of paragraphs) {
|
||||
if (paragraph.length > limit) {
|
||||
flush();
|
||||
for (const sentence of splitSentences(paragraph)) {
|
||||
if ((current.length + sentence.length + 1) > limit) flush();
|
||||
if (sentence.length > limit) {
|
||||
chunks.push(...this._chunkText(sentence, limit).map(part => part.trim()).filter(Boolean));
|
||||
} else {
|
||||
current = current ? `${current} ${sentence}` : sentence;
|
||||
}
|
||||
}
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
if ((current.length + paragraph.length + 2) > limit) flush();
|
||||
current = current ? `${current}\n\n${paragraph}` : paragraph;
|
||||
}
|
||||
flush();
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// Backward-compatible alias
|
||||
async sendAlert(message) {
|
||||
const result = await this.sendMessage(message);
|
||||
@@ -529,12 +581,7 @@ export class TelegramAlerter {
|
||||
try {
|
||||
const response = await handler(args, msg.message_id, msg);
|
||||
if (response) {
|
||||
const text = typeof response === 'string' ? response : response.text;
|
||||
const parseMode = typeof response === 'object' && Object.hasOwn(response, 'parseMode')
|
||||
? response.parseMode
|
||||
: undefined;
|
||||
const replyMarkup = typeof response === 'object' ? response.replyMarkup : null;
|
||||
await this.sendMessage(text, { chatId: replyChatId, replyToMessageId: msg.message_id, ...(parseMode !== undefined ? { parseMode } : {}), ...(replyMarkup ? { replyMarkup } : {}) });
|
||||
await this._sendHandlerResponse(response, { chatId: replyChatId, replyToMessageId: msg.message_id });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Telegram] Command ${command} error:`, err.message);
|
||||
@@ -555,16 +602,10 @@ export class TelegramAlerter {
|
||||
try {
|
||||
const response = await handler(text, msg);
|
||||
if (!response) return;
|
||||
const responseText = typeof response === 'string' ? response : response.text;
|
||||
const responseParseMode = typeof response === 'object' && Object.hasOwn(response, 'parseMode')
|
||||
? response.parseMode
|
||||
: parseMode;
|
||||
const replyMarkup = typeof response === 'object' ? response.replyMarkup : null;
|
||||
await this.sendMessage(responseText, {
|
||||
await this._sendHandlerResponse(response, {
|
||||
chatId: replyChatId,
|
||||
replyToMessageId: msg.message_id,
|
||||
parseMode: responseParseMode,
|
||||
...(replyMarkup ? { replyMarkup } : {}),
|
||||
parseMode,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[Telegram] AI chat error:', err.message);
|
||||
@@ -587,14 +628,10 @@ export class TelegramAlerter {
|
||||
try {
|
||||
const response = await this._callbackHandler(query.data, query);
|
||||
if (!response) return;
|
||||
const text = typeof response === 'string' ? response : response.text;
|
||||
const parseMode = typeof response === 'object' && Object.hasOwn(response, 'parseMode') ? response.parseMode : null;
|
||||
const replyMarkup = typeof response === 'object' ? response.replyMarkup : null;
|
||||
await this.sendMessage(text, {
|
||||
await this._sendHandlerResponse(response, {
|
||||
chatId,
|
||||
replyToMessageId: query.message?.message_id,
|
||||
parseMode,
|
||||
...(replyMarkup ? { replyMarkup } : {}),
|
||||
parseMode: null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Telegram] Callback error:', error.message);
|
||||
@@ -611,6 +648,31 @@ export class TelegramAlerter {
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
|
||||
async _sendHandlerResponse(response, defaults = {}) {
|
||||
const text = typeof response === 'string' ? response : response.text;
|
||||
const messages = typeof response === 'object' ? response.messages : null;
|
||||
const parseMode = typeof response === 'object' && Object.hasOwn(response, 'parseMode')
|
||||
? response.parseMode
|
||||
: defaults.parseMode;
|
||||
const replyMarkup = typeof response === 'object' ? response.replyMarkup : null;
|
||||
const common = {
|
||||
chatId: defaults.chatId,
|
||||
replyToMessageId: defaults.replyToMessageId,
|
||||
...(replyMarkup ? { replyMarkup } : {}),
|
||||
};
|
||||
if (typeof response === 'object' && response.conversation) {
|
||||
return this.sendConversation(messages || text, {
|
||||
...common,
|
||||
maxChunkChars: response.maxChunkChars,
|
||||
delayMs: response.delayMs,
|
||||
});
|
||||
}
|
||||
return this.sendMessage(text, {
|
||||
...common,
|
||||
...(parseMode !== undefined ? { parseMode } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
async _initializeBotCommands() {
|
||||
await this._loadBotIdentity();
|
||||
|
||||
@@ -928,3 +990,24 @@ function parseJSON(text) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function stripVisibleMarkdown(text) {
|
||||
return String(text || '')
|
||||
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
||||
.replace(/__([^_]+)__/g, '$1')
|
||||
.replace(/`([^`]+)`/g, '$1')
|
||||
.replace(/^#{1,6}\s+/gm, '')
|
||||
.replace(/^\s*[-*]\s+/gm, '- ')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function splitSentences(text) {
|
||||
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
||||
if (!normalized) return [];
|
||||
return normalized.match(/[^.!?\n]+[.!?]+(?:\s+|$)|[^.!?\n]+$/g)?.map(item => item.trim()).filter(Boolean) || [normalized];
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user