refactor(bot): centralize runtime state; support albums + safer vision handling
- make MessageStore.put() return StoredMessage and allow collectReplyChainText() to work with StoredMessage - move muted users + answers loading into Environment (add Answers model + GEMINI_IMAGE_MODEL) - extract message handling into processNewMessage() and add media-group (album) caching/downloading by unique_file_id - for Ollama: check model capabilities before sending images; use replyToMessage helper consistently - add /geminiGenImage command stub for Gemini image generation
This commit is contained in:
@@ -34,7 +34,8 @@ export class GeminiChat extends ChatCommand {
|
||||
|
||||
const chatId = msg.chat.id;
|
||||
|
||||
const messageParts = await collectReplyChainText(msg);
|
||||
const storedMsg = await MessageStore.get(chatId, msg.message_id);
|
||||
const messageParts = await collectReplyChainText(storedMsg);
|
||||
console.log("MESSAGE PARTS", messageParts);
|
||||
|
||||
const chatMessages = messageParts.map(part => {
|
||||
@@ -64,7 +65,7 @@ export class GeminiChat extends ChatCommand {
|
||||
if (messageParts[0].images?.length) {
|
||||
const images = messageParts[0].images;
|
||||
|
||||
images.forEach(image=>{
|
||||
images.forEach(image => {
|
||||
const base64Image = Buffer.from(fs.readFileSync(image)).toString("base64");
|
||||
input.push({
|
||||
type: "image",
|
||||
@@ -144,6 +145,10 @@ export class GeminiChat extends ChatCommand {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "image": {
|
||||
const image = event.delta.data;
|
||||
console.log("image", image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {Requirements} from "../base/requirements";
|
||||
import {Requirement} from "../base/requirement";
|
||||
import {Message} from "typescript-telegram-bot-api";
|
||||
import {googleAi} from "../index";
|
||||
import {logError, replyToMessage} from "../util/utils";
|
||||
import {Environment} from "../common/environment";
|
||||
|
||||
export class GeminiGenerateImage extends ChatCommand {
|
||||
command = "geminiGenImage";
|
||||
argsMode = "required" as const;
|
||||
|
||||
title = "/geminiGenImage";
|
||||
description = "Generate image with Gemini";
|
||||
|
||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||
|
||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||
console.log("match", match);
|
||||
|
||||
const prompt = match?.[3];
|
||||
return this.executeGenImage(msg, prompt);
|
||||
}
|
||||
|
||||
async executeGenImage(msg: Message, text: string): Promise<void> {
|
||||
if (!text || text.trim().length === 0) return;
|
||||
|
||||
let waitMessage: Message;
|
||||
|
||||
try {
|
||||
waitMessage = await replyToMessage({
|
||||
message: msg,
|
||||
text: Environment.genImageText,
|
||||
});
|
||||
|
||||
const interaction = await googleAi.interactions.create({
|
||||
model: Environment.GEMINI_IMAGE_MODEL,
|
||||
response_modalities: ["image"],
|
||||
input: text,
|
||||
});
|
||||
|
||||
interaction.outputs?.forEach((output, index) => {
|
||||
if (output.type === "image") {
|
||||
// const image = output.data;
|
||||
console.log(`Image output ${index + 1}:`, output);
|
||||
} else {
|
||||
console.log(`Output ${index + 1}: ${output}`);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
|
||||
await replyToMessage({
|
||||
message: waitMessage,
|
||||
text: `Произошла ошибка!\n${e.toString()}`,
|
||||
disableLinkPreview: true
|
||||
}).catch(logError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import {addMute} from "../db/database";
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {Requirements} from "../base/requirements";
|
||||
import {Requirement} from "../base/requirement";
|
||||
@@ -35,7 +34,7 @@ export class Ignore extends ChatCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await addMute(id)) {
|
||||
if (await Environment.addMute(id)) {
|
||||
await oldSendMessage(msg, text + " в муте! 🔇").catch(logError);
|
||||
} else {
|
||||
await oldSendMessage(msg, text + " уже в муте 🤔").catch(logError);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
escapeMarkdownV2Text,
|
||||
logError,
|
||||
oldReplyToMessage,
|
||||
photoPathByUniqueId,
|
||||
startIntervalEditor
|
||||
} from "../util/utils";
|
||||
import {Environment} from "../common/environment";
|
||||
@@ -33,7 +34,8 @@ export class MistralChat extends ChatCommand {
|
||||
|
||||
const chatId = msg.chat.id;
|
||||
|
||||
const messageParts = await collectReplyChainText(msg);
|
||||
const storedMsg = await MessageStore.get(chatId, msg.message_id);
|
||||
const messageParts = await collectReplyChainText(storedMsg);
|
||||
console.log("MESSAGE PARTS", messageParts);
|
||||
|
||||
const chatMessages = messageParts.map(part => {
|
||||
@@ -43,8 +45,8 @@ export class MistralChat extends ChatCommand {
|
||||
text: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content,
|
||||
});
|
||||
|
||||
if (part.images && part.images.length > 0) {
|
||||
const base64Image = Buffer.from(fs.readFileSync(part.images[0])).toString("base64");
|
||||
for (const image of part.images) {
|
||||
const base64Image = Buffer.from(fs.readFileSync(photoPathByUniqueId(image))).toString("base64");
|
||||
content.push({
|
||||
type: "image_url",
|
||||
imageUrl: "data:image/jpeg;base64," + base64Image
|
||||
|
||||
+26
-10
@@ -1,17 +1,19 @@
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {Message} from "typescript-telegram-bot-api";
|
||||
import {abortOllamaRequest, bot, getOllamaRequest, ollama, ollamaRequests} from "../index";
|
||||
import {abortOllamaRequest, bot, chatCommands, getOllamaRequest, ollama, ollamaRequests} from "../index";
|
||||
import {
|
||||
collectReplyChainText,
|
||||
escapeMarkdownV2Text,
|
||||
logError,
|
||||
oldReplyToMessage,
|
||||
replyToMessage,
|
||||
startIntervalEditor
|
||||
} from "../util/utils";
|
||||
import {Environment} from "../common/environment";
|
||||
import {MessageStore} from "../common/message-store";
|
||||
import {Cancel} from "../callback_commands/cancel";
|
||||
import {OllamaCancel} from "../callback_commands/ollama-cancel";
|
||||
import {OllamaGetModel} from "./ollama-get-model";
|
||||
|
||||
export class OllamaChat extends ChatCommand {
|
||||
command = "ollama";
|
||||
@@ -30,7 +32,8 @@ export class OllamaChat extends ChatCommand {
|
||||
|
||||
const chatId = msg.chat.id;
|
||||
|
||||
const messageParts = await collectReplyChainText(msg);
|
||||
const storedMsg = await MessageStore.get(chatId, msg.message_id);
|
||||
const messageParts = await collectReplyChainText(storedMsg);
|
||||
console.log("MESSAGE PARTS", messageParts);
|
||||
|
||||
const chatMessages = messageParts.map(part => {
|
||||
@@ -52,19 +55,32 @@ export class OllamaChat extends ChatCommand {
|
||||
return total + (curr.images?.length ?? 0);
|
||||
}, 0);
|
||||
|
||||
if (imagesCount) {
|
||||
try {
|
||||
const modelInfo = await chatCommands.find(c => c instanceof OllamaGetModel).loadModelInfo();
|
||||
if (modelInfo) {
|
||||
const caps = modelInfo.capabilities || [];
|
||||
if (!caps.includes("vision")) {
|
||||
await replyToMessage({
|
||||
message: msg,
|
||||
text: "Моя текущая модель не умеет анализировать изображения 🥹"
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID();
|
||||
const cancelMarkup = {inline_keyboard: [[Cancel.withData(new OllamaCancel().data + " " + uuid).asButton()]]};
|
||||
|
||||
waitMessage = await bot.sendMessage({
|
||||
chat_id: chatId,
|
||||
waitMessage = await replyToMessage({
|
||||
message: msg,
|
||||
text: imagesCount ?
|
||||
imagesCount > 1 ? Environment.analyzingPicturesText : Environment.analyzingPictureText
|
||||
: Environment.waitText,
|
||||
|
||||
reply_parameters: {
|
||||
chat_id: chatId,
|
||||
message_id: msg.message_id
|
||||
}
|
||||
: Environment.waitText
|
||||
});
|
||||
|
||||
const stream = await ollama.chat({
|
||||
|
||||
@@ -3,6 +3,7 @@ import {Message} from "typescript-telegram-bot-api";
|
||||
import {boolToEmoji, logError, replyToMessage} from "../util/utils";
|
||||
import {Environment} from "../common/environment";
|
||||
import {ollama} from "../index";
|
||||
import {ShowResponse} from "ollama";
|
||||
|
||||
export class OllamaGetModel extends ChatCommand {
|
||||
title = "/ollamaGetModel";
|
||||
@@ -10,7 +11,7 @@ export class OllamaGetModel extends ChatCommand {
|
||||
|
||||
async execute(msg: Message): Promise<void> {
|
||||
try {
|
||||
const showResponse = await ollama.show({model: Environment.OLLAMA_MODEL});
|
||||
const showResponse = await this.loadModelInfo();
|
||||
|
||||
const caps = showResponse.capabilities;
|
||||
|
||||
@@ -27,4 +28,8 @@ export class OllamaGetModel extends ChatCommand {
|
||||
await replyToMessage({message: msg, text: e.toString()}).catch(logError);
|
||||
}
|
||||
}
|
||||
|
||||
async loadModelInfo(): Promise<ShowResponse | null> {
|
||||
return ollama.show({model: Environment.OLLAMA_MODEL});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {Message} from "typescript-telegram-bot-api";
|
||||
import {logError, randomValue, oldReplyToMessage} from "../util/utils";
|
||||
import {prefixAnswers} from "../db/database";
|
||||
import {logError, randomValue, replyToMessage} from "../util/utils";
|
||||
import {Environment} from "../common/environment";
|
||||
|
||||
export class PrefixResponse extends ChatCommand {
|
||||
async execute(msg: Message): Promise<void> {
|
||||
await oldReplyToMessage(msg, randomValue(prefixAnswers)).catch(logError);
|
||||
await replyToMessage({message: msg, text: randomValue(Environment.ANSWERS.prefix)}).catch(logError);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {Message} from "typescript-telegram-bot-api";
|
||||
import {logError, randomValue, oldReplyToMessage} from "../util/utils";
|
||||
import {testAnswers} from "../db/database";
|
||||
import {logError, oldReplyToMessage, randomValue} from "../util/utils";
|
||||
import {Environment} from "../common/environment";
|
||||
|
||||
export class Test extends ChatCommand {
|
||||
regexp = /^(test|тест|еуые|ntcn|инноке(нтий|ш|нтич))/i;
|
||||
@@ -9,6 +9,6 @@ export class Test extends ChatCommand {
|
||||
description = "System functionality check";
|
||||
|
||||
async execute(msg: Message) {
|
||||
await oldReplyToMessage(msg, randomValue(testAnswers) || "а").catch(logError);
|
||||
await oldReplyToMessage(msg, randomValue(Environment.ANSWERS.test) || "а").catch(logError);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import {removeMute} from "../db/database";
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {Requirements} from "../base/requirements";
|
||||
import {Requirement} from "../base/requirement";
|
||||
@@ -34,7 +33,7 @@ export class Unignore extends ChatCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await removeMute(id)) {
|
||||
if (await Environment.removeMute(id)) {
|
||||
await oldSendMessage(msg, text + " больше не в муте! 🔈").catch(logError);
|
||||
} else {
|
||||
await oldSendMessage(msg, text + " не был в муте 🤔").catch(logError);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {logError, oldSendMessage, randomValue} from "../util/utils";
|
||||
import {betterAnswers} from "../db/database";
|
||||
import {Message} from "typescript-telegram-bot-api";
|
||||
import {Environment} from "../common/environment";
|
||||
|
||||
export class WhatBetter extends ChatCommand {
|
||||
command = ["what", "что"];
|
||||
@@ -19,7 +19,7 @@ export class WhatBetter extends ChatCommand {
|
||||
const a = m[2].trim();
|
||||
const b = m[4].trim();
|
||||
|
||||
const text = `${randomValue(betterAnswers)} ${randomValue([a, b])}`;
|
||||
const text = `${randomValue(Environment.ANSWERS.better)} ${randomValue([a, b])}`;
|
||||
|
||||
await oldSendMessage(msg, text).catch(logError);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user