import {Message} from "typescript-telegram-bot-api"; import {Environment} from "../common/environment"; import {bot, googleAi} from "../index"; import {MessageStore} from "../common/message-store"; import {Requirements} from "../base/requirements"; import {Requirement} from "../base/requirement"; import {ApiError} from "@google/genai"; import { collectReplyChainText, escapeMarkdownV2Text, logError, oldReplyToMessage, startIntervalEditor } from "../util/utils"; import {ChatCommand} from "../base/chat-command"; export class GeminiChat extends ChatCommand { command = "gemini"; argsMode = "required" as const; requirements = Requirements.Build(Requirement.BOT_CREATOR); title = "/gemini"; description = "Chat with AI (Gemini)"; async execute(msg: Message, match?: RegExpExecArray): Promise { console.log("match", match); return this.executeGemini(msg, match?.[3]); } async executeGemini(msg: Message, text: string): Promise { if (!text || text.trim().length === 0) return; const chatId = msg.chat.id; const storedMsg = await MessageStore.get(chatId, msg.message_id); const messageParts = await collectReplyChainText(storedMsg); console.log("MESSAGE PARTS", messageParts); const chatMessages = messageParts.map(part => { return { role: part.bot ? "assistant" : "user", content: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content }; }); chatMessages.reverse(); chatMessages.unshift({role: "system", content: Environment.SYSTEM_PROMPT}); let chatContent = ""; for (const part of chatMessages) { chatContent += `${part.role.toUpperCase()}:\n${part.content}\n\n`; } chatContent = chatContent.trim(); const input = []; input.push( { type: "text", text: chatContent } ); // TODO: 12/02/2026, Danil Nikolaev: support for multiple images if (messageParts.some(p => p.images?.length)) { const firstImages = messageParts.find(p => p.images?.length)?.images ?? []; firstImages.forEach(image => { input.push({ type: "image", data: image, mime_type: "image/png" }); }); } let waitMessage: Message; const startTime = Date.now(); try { waitMessage = await bot.sendMessage({ chat_id: chatId, text: Environment.waitThinkText, reply_parameters: { chat_id: chatId, message_id: msg.message_id } }); const stream = await googleAi.interactions.create({ model: Environment.GEMINI_MODEL, input: input, stream: true }); let currentText = ""; let shouldBreak = false; const editor = startIntervalEditor({ intervalMs: 4500, getText: () => currentText, editFn: async (text) => { await bot.editMessageText( { chat_id: chatId, message_id: waitMessage.message_id, text: escapeMarkdownV2Text(text), parse_mode: "Markdown" } ).catch(logError); console.log("editMessageText", text); waitMessage.reply_to_message = msg; waitMessage.text = text; await MessageStore.put(waitMessage); }, onStop: async () => { } }); await editor.tick(); try { for await (const event of stream) { switch (event.event_type) { case "content.delta": switch (event.delta?.type) { case "text": { const text = event.delta.text; currentText += text; if (currentText.length > 4096) { currentText = currentText.slice(0, 4093) + "..."; shouldBreak = true; } console.log("messageText", currentText); console.log("length", currentText.length); if (shouldBreak) { console.log("break", true); break; } break; } case "image": { const image = event.delta.data; console.log("image", image); } } } } } finally { await editor.tick(); await editor.stop(); if (!shouldBreak) { console.log("ended", true); } const diff = Math.abs(Date.now() - startTime) / 1000.0; console.log("time", diff); waitMessage.reply_to_message = msg; waitMessage.text = currentText; await MessageStore.put(waitMessage); await oldReplyToMessage(waitMessage, `⏱️ ${diff}s`); } } catch (error) { logError(error); if (error instanceof ApiError) { if (error.status === 429) { await oldReplyToMessage(waitMessage, "На сегодня всё, лимиты закончились.").catch(logError); return; } } await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError); } } }