commands: switch AI commands to unified runtime
This commit is contained in:
@@ -0,0 +1,142 @@
|
|||||||
|
import {CallbackCommand} from "../base/callback-command";
|
||||||
|
import {CallbackQuery, InlineKeyboardMarkup, Message} from "typescript-telegram-bot-api";
|
||||||
|
import {abortAiRequest, getAiCancelRequest} from "../ai/cancel-registry";
|
||||||
|
import {Requirements} from "../base/requirements";
|
||||||
|
import {Requirement} from "../base/requirement";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {MessageStore} from "../common/message-store";
|
||||||
|
import {bot} from "../index";
|
||||||
|
import {buildCancelledGenerationText, logError} from "../util/utils";
|
||||||
|
import {enqueueTelegramApiCall} from "../util/telegram-api-queue";
|
||||||
|
import {prepareTelegramMarkdownV2} from "../util/markdown-v2-renderer";
|
||||||
|
import {buildAiRegenerateCallbackData} from "../ai/regenerate-callback";
|
||||||
|
import {isAiProviderConfigured, resolveEffectiveAiProviderForUser} from "../common/user-ai-settings";
|
||||||
|
|
||||||
|
const TELEGRAM_TEXT_LIMIT = 4096;
|
||||||
|
const TELEGRAM_CAPTION_LIMIT = 1024;
|
||||||
|
|
||||||
|
export class AiCancel extends CallbackCommand {
|
||||||
|
data = "/cancel_ai";
|
||||||
|
text = Environment.aiCancelCallbackText;
|
||||||
|
|
||||||
|
requirements = Requirements.Build(Requirement.SAME_USER);
|
||||||
|
|
||||||
|
async execute(query: CallbackQuery): Promise<void> {
|
||||||
|
if (!query.message || !query.data) return;
|
||||||
|
|
||||||
|
const parsed = this.parseCallbackData(query.data);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
const request = getAiCancelRequest(parsed.requestId);
|
||||||
|
if (!request) {
|
||||||
|
await this.markMessageAsCancelled(query, parsed.provider);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.fromId !== query.from.id && query.from.id !== Environment.CREATOR_ID) return;
|
||||||
|
|
||||||
|
const cancelled = await abortAiRequest(parsed.requestId);
|
||||||
|
if (!cancelled) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseCallbackData(data: string): { requestId: string; provider?: AiProvider } | null {
|
||||||
|
const [, requestId, provider] = data.split(/\s+/);
|
||||||
|
if (!requestId) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestId,
|
||||||
|
provider: Object.values(AiProvider).includes(provider as AiProvider) ? provider as AiProvider : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async markMessageAsCancelled(query: CallbackQuery, providerFromCallback?: AiProvider): Promise<void> {
|
||||||
|
const callbackMessage = query.message;
|
||||||
|
if (!callbackMessage || callbackMessage.date === 0) return;
|
||||||
|
const message = callbackMessage as Message;
|
||||||
|
|
||||||
|
const stored = await MessageStore.get(message.chat.id, message.message_id).catch(e => {
|
||||||
|
logError(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
const sourceFromId = await this.resolveSourceFromId(message, stored).catch(e => {
|
||||||
|
logError(e);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
const regenerateProvider = providerFromCallback && isAiProviderConfigured(providerFromCallback)
|
||||||
|
? providerFromCallback
|
||||||
|
: await resolveEffectiveAiProviderForUser(sourceFromId ?? query.from.id);
|
||||||
|
const providerName = (providerFromCallback ?? regenerateProvider).toLowerCase();
|
||||||
|
const isCaption = this.isCaptionMessage(message);
|
||||||
|
const limit = isCaption ? TELEGRAM_CAPTION_LIMIT : TELEGRAM_TEXT_LIMIT;
|
||||||
|
const baseText = stored?.text ?? message.text ?? message.caption ?? "";
|
||||||
|
const cancelledText = buildCancelledGenerationText(baseText, providerName, limit);
|
||||||
|
const replyMarkup = this.regenerateKeyboard(regenerateProvider);
|
||||||
|
const formatted = prepareTelegramMarkdownV2(cancelledText, {mode: "final"});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = isCaption
|
||||||
|
? await enqueueTelegramApiCall(
|
||||||
|
() => bot.editMessageCaption({
|
||||||
|
chat_id: message.chat.id,
|
||||||
|
message_id: message.message_id,
|
||||||
|
caption: formatted,
|
||||||
|
parse_mode: "MarkdownV2",
|
||||||
|
reply_markup: replyMarkup,
|
||||||
|
}),
|
||||||
|
{method: "editMessageCaption", chatId: message.chat.id, chatType: message.chat.type}
|
||||||
|
)
|
||||||
|
: await enqueueTelegramApiCall(
|
||||||
|
() => bot.editMessageText({
|
||||||
|
chat_id: message.chat.id,
|
||||||
|
message_id: message.message_id,
|
||||||
|
text: formatted,
|
||||||
|
parse_mode: "MarkdownV2",
|
||||||
|
reply_markup: replyMarkup,
|
||||||
|
}),
|
||||||
|
{method: "editMessageText", chatId: message.chat.id, chatType: message.chat.type}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result && result !== true) {
|
||||||
|
await MessageStore.put({...result, text: cancelledText} as Message);
|
||||||
|
} else {
|
||||||
|
await MessageStore.put({
|
||||||
|
chatId: message.chat.id,
|
||||||
|
id: message.message_id,
|
||||||
|
replyToMessageId: stored?.replyToMessageId ?? this.replyToMessageId(message),
|
||||||
|
fromId: message.from?.id ?? stored?.fromId ?? 0,
|
||||||
|
text: cancelledText,
|
||||||
|
date: message.date ?? stored?.date ?? Math.floor(Date.now() / 1000),
|
||||||
|
photoMaxSizeFilePath: stored?.photoMaxSizeFilePath,
|
||||||
|
attachments: stored?.attachments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private regenerateKeyboard(provider: AiProvider): InlineKeyboardMarkup {
|
||||||
|
return {
|
||||||
|
inline_keyboard: [[{
|
||||||
|
text: Environment.regenerateText,
|
||||||
|
callback_data: buildAiRegenerateCallbackData(provider),
|
||||||
|
}]],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveSourceFromId(message: Message, stored: Awaited<ReturnType<typeof MessageStore.get>>): Promise<number | undefined> {
|
||||||
|
const reply = "reply_to_message" in message ? message.reply_to_message : undefined;
|
||||||
|
if (reply?.from?.id) return reply.from.id;
|
||||||
|
|
||||||
|
const source = await MessageStore.get(message.chat.id, stored?.replyToMessageId);
|
||||||
|
return source?.fromId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private replyToMessageId(message: Message): number | undefined {
|
||||||
|
return "reply_to_message" in message ? message.reply_to_message?.message_id : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isCaptionMessage(message: Message): boolean {
|
||||||
|
return message.caption !== undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import {CallbackQuery, Message} from "typescript-telegram-bot-api";
|
||||||
|
import {CallbackCommand} from "../base/callback-command";
|
||||||
|
import {Requirements} from "../base/requirements";
|
||||||
|
import {Requirement} from "../base/requirement";
|
||||||
|
import {MessageStore} from "../common/message-store";
|
||||||
|
import {StoredMessage} from "../model/stored-message";
|
||||||
|
import {cutPrefixes, logError} from "../util/utils";
|
||||||
|
import {runUnifiedAi} from "../ai/unified-ai-runner";
|
||||||
|
import {AI_REGENERATE_CALLBACK, parseAiRegenerateCallbackData} from "../ai/regenerate-callback";
|
||||||
|
import {isAiProviderConfigured, resolveEffectiveAiProviderForUser} from "../common/user-ai-settings";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
|
export class AiRegenerate extends CallbackCommand {
|
||||||
|
data = AI_REGENERATE_CALLBACK;
|
||||||
|
text = Environment.aiRegenerateCallbackText;
|
||||||
|
|
||||||
|
requirements = Requirements.Build(Requirement.SAME_USER);
|
||||||
|
|
||||||
|
async execute(query: CallbackQuery): Promise<void> {
|
||||||
|
if (!query.message || !query.data) return;
|
||||||
|
|
||||||
|
const parsed = parseAiRegenerateCallbackData(query.data);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
const source = await this.resolveSourceMessage(query);
|
||||||
|
if (!source) return;
|
||||||
|
|
||||||
|
const sourceFromId = source.stored?.fromId ?? source.message.from?.id;
|
||||||
|
if (!sourceFromId || (sourceFromId !== query.from.id && query.from.id !== Environment.CREATOR_ID)) return;
|
||||||
|
|
||||||
|
const provider = isAiProviderConfigured(parsed.provider)
|
||||||
|
? parsed.provider
|
||||||
|
: await resolveEffectiveAiProviderForUser(source.message.from?.id ?? query.from.id);
|
||||||
|
const text = cutPrefixes(source.stored ?? source.message) ?? "";
|
||||||
|
|
||||||
|
runUnifiedAi({
|
||||||
|
provider,
|
||||||
|
msg: source.message,
|
||||||
|
text,
|
||||||
|
stream: true,
|
||||||
|
think: parsed.think,
|
||||||
|
targetMessage: query.message,
|
||||||
|
}).catch(logError);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveSourceMessage(query: CallbackQuery): Promise<{
|
||||||
|
message: Message;
|
||||||
|
stored: StoredMessage | null;
|
||||||
|
} | null> {
|
||||||
|
const responseMessage = query.message;
|
||||||
|
if (!responseMessage) return null;
|
||||||
|
|
||||||
|
const directSource = "reply_to_message" in responseMessage ? responseMessage.reply_to_message : undefined;
|
||||||
|
if (directSource) {
|
||||||
|
const stored = await MessageStore.put(directSource).catch(e => {
|
||||||
|
logError(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return {message: directSource, stored};
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedResponse = await MessageStore.get(responseMessage.chat.id, responseMessage.message_id);
|
||||||
|
const storedSource = await MessageStore.get(responseMessage.chat.id, storedResponse?.replyToMessageId);
|
||||||
|
if (!storedSource) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: this.storedToMessage(storedSource, responseMessage, query),
|
||||||
|
stored: storedSource,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private storedToMessage(stored: StoredMessage, responseMessage: Message, query: CallbackQuery): Message {
|
||||||
|
return {
|
||||||
|
message_id: stored.id,
|
||||||
|
chat: responseMessage.chat,
|
||||||
|
date: stored.date,
|
||||||
|
from: query.from.id === stored.fromId
|
||||||
|
? query.from
|
||||||
|
: {id: stored.fromId, is_bot: false, first_name: ""},
|
||||||
|
text: stored.text ?? undefined,
|
||||||
|
} as Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import {CallbackCommand} from "../base/callback-command";
|
|
||||||
import {CallbackQuery} from "typescript-telegram-bot-api";
|
|
||||||
import {abortOllamaRequest, bot, getOllamaRequest} from "../index";
|
|
||||||
import {escapeMarkdownV2Text, logError} from "../util/utils";
|
|
||||||
import {MessageStore} from "../common/message-store";
|
|
||||||
import {StoredMessage} from "../model/stored-message";
|
|
||||||
import {Requirements} from "../base/requirements";
|
|
||||||
import {Requirement} from "../base/requirement";
|
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
|
|
||||||
export class OllamaCancel extends CallbackCommand {
|
|
||||||
|
|
||||||
data = "/cancel_ollama";
|
|
||||||
text = "Cancel Ollama generation";
|
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.SAME_USER);
|
|
||||||
|
|
||||||
async execute(query: CallbackQuery): Promise<void> {
|
|
||||||
if (!query.message || !query.data) return;
|
|
||||||
|
|
||||||
const chatId = query.message.chat.id;
|
|
||||||
const fromId = query.from.id;
|
|
||||||
const messageId = query.message.message_id;
|
|
||||||
|
|
||||||
const uuid = query.data.split(" ")[1];
|
|
||||||
if (!uuid) return;
|
|
||||||
|
|
||||||
const request = getOllamaRequest(uuid);
|
|
||||||
if (request) {
|
|
||||||
if (request.fromId !== fromId && fromId !== Environment.CREATOR_ID) return;
|
|
||||||
|
|
||||||
const aborted = abortOllamaRequest(uuid);
|
|
||||||
console.log(`aborted request ${uuid}:`, aborted);
|
|
||||||
} else {
|
|
||||||
console.log(`no request with uuid "${uuid}" found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let msg: StoredMessage | null = null;
|
|
||||||
try {
|
|
||||||
msg = await MessageStore.get(chatId, messageId);
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Message for ${chatId}-${messageId}:`, msg);
|
|
||||||
|
|
||||||
let content: string | null = null;
|
|
||||||
|
|
||||||
if (msg?.text?.trim()?.length) {
|
|
||||||
content = msg?.text.trim();
|
|
||||||
if (content.length + Environment.ollamaCancelledText.length > 4096) {
|
|
||||||
content = content.substring(0, 4096 - Environment.ollamaCancelledText.length - 2) + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newText = `${content ? content : ""}${Environment.ollamaCancelledText}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await bot.editMessageText({
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: messageId,
|
|
||||||
text: escapeMarkdownV2Text(newText),
|
|
||||||
parse_mode: "MarkdownV2",
|
|
||||||
reply_markup: {inline_keyboard: []},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (msg) {
|
|
||||||
await MessageStore.put(msg);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+10
-181
@@ -1,196 +1,25 @@
|
|||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {Environment} from "../common/environment";
|
import {ChatCommand} from "../base/chat-command";
|
||||||
import {bot, googleAi} from "../index";
|
|
||||||
import {MessageStore} from "../common/message-store";
|
|
||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
import {
|
import {runUnifiedAi} from "../ai/unified-ai-runner";
|
||||||
collectReplyChainText,
|
import {Environment} from "../common/environment";
|
||||||
escapeMarkdownV2Text,
|
|
||||||
logError,
|
|
||||||
oldReplyToMessage,
|
|
||||||
replyToMessage,
|
|
||||||
startIntervalEditor
|
|
||||||
} from "../util/utils";
|
|
||||||
import {ChatCommand} from "../base/chat-command";
|
|
||||||
import {ApiError} from "@google/genai";
|
|
||||||
|
|
||||||
export class GeminiChat extends ChatCommand {
|
export class GeminiChat extends ChatCommand {
|
||||||
command = "gemini";
|
command = ["gemini", "gemini-chat"];
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
title = "/gemini";
|
title = Environment.commandTitles.geminiChat;
|
||||||
description = "Chat with AI (Gemini)";
|
description = Environment.commandDescriptions.geminiChat;
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||||
console.log("match", match);
|
|
||||||
return this.executeGemini(msg, match?.[3] || "");
|
return this.executeGemini(msg, match?.[3] || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeGemini(msg: Message, text: string): Promise<void> {
|
async executeGemini(msg: Message, text: string, stream: boolean = true): Promise<void> {
|
||||||
if (!text || !text.trim().length) return;
|
await runUnifiedAi({provider: AiProvider.GEMINI, msg, text, stream});
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (Environment.SYSTEM_PROMPT && Environment.USE_SYSTEM_PROMPT) {
|
|
||||||
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 | null = null;
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const imagesCount = input.some(e => e.type === "image");
|
|
||||||
|
|
||||||
waitMessage = await bot.sendMessage({
|
|
||||||
chat_id: chatId,
|
|
||||||
text: imagesCount ? Environment.analyzingPictureText : Environment.waitThinkText,
|
|
||||||
reply_parameters: {
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: msg.message_id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const stream = await googleAi.interactions.create({
|
|
||||||
model: Environment.GEMINI_MODEL,
|
|
||||||
input: input as any,
|
|
||||||
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: <number>waitMessage?.message_id,
|
|
||||||
text: escapeMarkdownV2Text(text),
|
|
||||||
parse_mode: "MarkdownV2"
|
|
||||||
}
|
|
||||||
).catch(logError);
|
|
||||||
|
|
||||||
console.log("editMessageText", text);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (Environment.SEND_TIME_TOOK) {
|
|
||||||
await replyToMessage({message: waitMessage, text: `⏱️ ${diff}s`});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
logError(e);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
if (e instanceof ApiError) {
|
|
||||||
if (e.status === 429) {
|
|
||||||
await oldReplyToMessage(waitMessage, "На сегодня всё, лимиты закончились.").catch(logError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import {Command} from "../base/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 Command {
|
|
||||||
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) return;
|
|
||||||
|
|
||||||
let waitMessage: Message | null = null;
|
|
||||||
|
|
||||||
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: any) {
|
|
||||||
logError(e);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
await replyToMessage({
|
|
||||||
message: waitMessage,
|
|
||||||
text: `Произошла ошибка!\n${e.toString()}`,
|
|
||||||
link_preview_options: {is_disabled: true}
|
|
||||||
}).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,15 @@
|
|||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {googleAi} from "../index";
|
|
||||||
import {AiModelCapabilities} from "../model/ai-model-capabilities";
|
|
||||||
|
|
||||||
export class GeminiGetModel extends Command {
|
export class GeminiGetModel extends Command {
|
||||||
title = "/geminiGetModel";
|
title = Environment.commandTitles.geminiGetModel;
|
||||||
description = "Get current Gemini model";
|
description = Environment.commandDescriptions.geminiGetModel;
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
await replyToMessage({message: msg, text: `Текущая модель: "${Environment.GEMINI_MODEL}"`}).catch(logError);
|
await replyToMessage({message: msg, text: await formatRuntimeModelInfo(AiProvider.GEMINI)}).catch(logError);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async getModelCapabilities(): Promise<AiModelCapabilities | null> {
|
|
||||||
try {
|
|
||||||
const info = await googleAi.models.get({model: Environment.GEMINI_MODEL});
|
|
||||||
console.log(info);
|
|
||||||
|
|
||||||
return {
|
|
||||||
vision: {supported: true},
|
|
||||||
ocr: undefined,
|
|
||||||
thinking: {supported: info.thinking},
|
|
||||||
tools: undefined
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,35 +2,27 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {googleAi} from "../index";
|
import {escapeHtml, logError, replyToMessage} from "../util/utils";
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {listProviderModels} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class GeminiListModels extends Command {
|
export class GeminiListModels extends Command {
|
||||||
title = "/geminiListModels";
|
title = Environment.commandTitles.geminiListModels;
|
||||||
description = "List all Gemini models";
|
description = Environment.commandDescriptions.geminiListModels;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const listResponse = await googleAi.models.list();
|
const models = (await listProviderModels(AiProvider.GEMINI)).sort((a, b) => a.localeCompare(b));
|
||||||
console.log(listResponse);
|
const modelsString = escapeHtml(models.join("\n").substring(0, 4000));
|
||||||
|
const text = Environment.modelListHeaderText + "<blockquote expandable>" + modelsString + "</blockquote>";
|
||||||
|
|
||||||
const modelsString = listResponse.page
|
await replyToMessage({message: msg, text, parse_mode: "HTML"});
|
||||||
.sort((a, b) => (a.name || "").localeCompare((b.name || "")))
|
|
||||||
.map(e => `${e.name}`)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const text = "Доступные модели:\n\n" + "<blockquote expandable>" + modelsString + "</blockquote>";
|
|
||||||
|
|
||||||
await replyToMessage({
|
|
||||||
message: msg,
|
|
||||||
text: text,
|
|
||||||
parse_mode: "HTML"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e);
|
logError(e);
|
||||||
await replyToMessage({message: msg, text: "Не получилось загрузить список моделей"}).catch(logError);
|
await replyToMessage({message: msg, text: Environment.modelListLoadFailedText}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,28 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {getRuntimeModel, setRuntimeModel, formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class GeminiSetModel extends Command {
|
export class GeminiSetModel extends Command {
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
title = "/geminiSetModel";
|
title = Environment.commandTitles.geminiSetModel;
|
||||||
description = "Set Gemini model";
|
description = Environment.commandDescriptions.geminiSetModel;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
||||||
const newModel = match?.[3];
|
const newModel = match?.[3]?.trim();
|
||||||
Environment.setGeminiModel(newModel || Environment.GEMINI_MODEL);
|
if (newModel) setRuntimeModel(AiProvider.GEMINI, newModel);
|
||||||
|
|
||||||
const text = newModel ? `Выбрана модель "${newModel}"`
|
const model = getRuntimeModel(AiProvider.GEMINI);
|
||||||
: `Модель не задана. Будет использоваться стандартная модель "${Environment.GEMINI_MODEL}".`;
|
const text = newModel
|
||||||
|
? Environment.getSelectedModelWithInfoText(model, await formatRuntimeModelInfo(AiProvider.GEMINI))
|
||||||
|
: Environment.getModelIsNotSetCurrentText(model);
|
||||||
|
|
||||||
await replyToMessage({message: msg, text: text}).catch(logError);
|
await replyToMessage({message: msg, text}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-38
@@ -2,67 +2,76 @@ import {ChatCommand} from "../base/chat-command";
|
|||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {callbackCommands, commands} from "../index";
|
import {callbackCommands, commands} from "../index";
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {boolToEmoji, getCurrentModel, getCurrentModelCapabilities, logError, replyToMessage} from "../util/utils";
|
import {getCurrentModel, logError, replyToMessage} from "../util/utils";
|
||||||
import {AiModelCapabilities} from "../model/ai-model-capabilities";
|
import {AiModelCapabilities} from "../model/ai-model-capabilities";
|
||||||
import {AiProvider} from "../model/ai-provider";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
|
import {formatRuntimeModelInfo, getRuntimeCapabilities} from "../ai/provider-model-runtime";
|
||||||
|
import {getProviderTools} from "../ai/tool-mappers";
|
||||||
|
import {prepareTelegramMarkdownV2} from "../util/markdown-v2-renderer";
|
||||||
|
|
||||||
export class Info extends Command {
|
export class Info extends Command {
|
||||||
command = ["info", "v"];
|
command = ["info", "v"];
|
||||||
|
|
||||||
title = "/info";
|
title = Environment.commandTitles.info;
|
||||||
description = "Info about bot";
|
description = Environment.commandDescriptions.info;
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
const aiProvider = Environment.DEFAULT_AI_PROVIDER;
|
const aiProvider = Environment.DEFAULT_AI_PROVIDER;
|
||||||
const aiModel = getCurrentModel();
|
const aiModel = getCurrentModel();
|
||||||
let aiModelCapabilities: AiModelCapabilities | null = {};
|
if (!aiModel) return;
|
||||||
|
let aiModelCapabilities: AiModelCapabilities | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
aiModelCapabilities = await getCurrentModelCapabilities();
|
aiModelCapabilities = await getRuntimeCapabilities(aiProvider, aiModel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e);
|
logError(e);
|
||||||
await replyToMessage({message: msg, text: `Произошла ошибка: ${e}`}).catch(logError);
|
await replyToMessage({message: msg, text: Environment.getErrorText(e)}).catch(logError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedProvidersLength = Object.keys(AiProvider).filter(key => isNaN(Number(key))).length;
|
||||||
|
|
||||||
const aiInfo = "```" +
|
const getAiInfo = async () => {
|
||||||
"AI\n" +
|
return Environment.getInfoAiBlockText(
|
||||||
`supported providers: ${Object.keys(AiProvider).filter(key => isNaN(Number(key))).length}\n\n` +
|
supportedProvidersLength,
|
||||||
|
await formatRuntimeModelInfo(aiProvider, aiModel, aiModelCapabilities),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
`provider: ${aiProvider.toLowerCase()}\n` +
|
const getToolsInfo = async () => {
|
||||||
`model: ${aiModel}\n\n` +
|
const tools = getProviderTools(aiProvider);
|
||||||
`vision${aiModelCapabilities?.vision?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.vision?.supported)}\n` +
|
return Environment.getInfoToolsBlockText(tools.map(t => t.function.name));
|
||||||
`ocr${aiModelCapabilities?.ocr?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.ocr?.supported)}\n` +
|
};
|
||||||
`thinking${aiModelCapabilities?.thinking?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.thinking?.supported)}\n` +
|
|
||||||
`tools${aiModelCapabilities?.tools?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.tools?.supported)}\n` +
|
|
||||||
`audio${aiModelCapabilities?.audio?.external ? "(ext)" : ""}: ${boolToEmoji(aiModelCapabilities?.audio?.supported)}` +
|
|
||||||
"```";
|
|
||||||
|
|
||||||
const cmds = commands.filter(c => !(c instanceof ChatCommand));
|
const getCommandsInfo = async () => {
|
||||||
const chatCmds = commands.filter(c => c instanceof ChatCommand);
|
const cmds = commands.filter(c => !(c instanceof ChatCommand));
|
||||||
const callbackCmds = callbackCommands;
|
const chatCmds = commands.filter(c => c instanceof ChatCommand);
|
||||||
|
const callbackCmds = callbackCommands;
|
||||||
|
const publicCmdsLength = cmds.filter(c => c.requirements?.isPublic()).length;
|
||||||
|
const privateCmdsLength = cmds.length - publicCmdsLength;
|
||||||
|
const chatCmdsLength = chatCmds.length;
|
||||||
|
const callbackCmdsLength = callbackCmds.length;
|
||||||
|
|
||||||
const publicCmdsLength = cmds.filter(c => c.requirements?.isPublic()).length;
|
return Environment.getInfoCommandsBlockText({
|
||||||
const privateCmdsLength = cmds.length - publicCmdsLength;
|
publicCommands: publicCmdsLength,
|
||||||
|
privateCommands: privateCmdsLength,
|
||||||
|
chatCommands: chatCmdsLength,
|
||||||
|
callbackCommands: callbackCmdsLength,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const chatCmdsLength = chatCmds.length;
|
|
||||||
|
|
||||||
const callbackCmdsLength = callbackCmds.length;
|
const finalText = [
|
||||||
|
await getAiInfo(),
|
||||||
|
await getToolsInfo(),
|
||||||
|
await getCommandsInfo()
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
const text =
|
await replyToMessage({
|
||||||
aiInfo + "\n\n" +
|
message: msg,
|
||||||
|
text: prepareTelegramMarkdownV2(finalText, {mode: "final"}),
|
||||||
"```" +
|
parse_mode: "MarkdownV2"
|
||||||
"Commands\n" +
|
}).catch(logError);
|
||||||
`Public: ${publicCmdsLength}\n` +
|
|
||||||
`Private: ${privateCmdsLength}\n` +
|
|
||||||
`Chat: ${chatCmdsLength}\n` +
|
|
||||||
`Callback: ${callbackCmdsLength}\n` +
|
|
||||||
"```"
|
|
||||||
;
|
|
||||||
|
|
||||||
await replyToMessage({message: msg, text: text, parse_mode: "Markdown"}).catch(logError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-170
@@ -1,185 +1,25 @@
|
|||||||
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
|
import {ChatCommand} from "../base/chat-command";
|
||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
import {
|
import {runUnifiedAi} from "../ai/unified-ai-runner";
|
||||||
collectReplyChainText,
|
|
||||||
escapeMarkdownV2Text,
|
|
||||||
logError,
|
|
||||||
oldReplyToMessage,
|
|
||||||
replyToMessage,
|
|
||||||
startIntervalEditor
|
|
||||||
} from "../util/utils";
|
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {bot, commands, mistralAi} from "../index";
|
|
||||||
import {MessageStore} from "../common/message-store";
|
|
||||||
import {ChatCommand} from "../base/chat-command";
|
|
||||||
import {MistralGetModel} from "./mistral-get-model";
|
|
||||||
|
|
||||||
export class MistralChat extends ChatCommand {
|
export class MistralChat extends ChatCommand {
|
||||||
command = "mistral";
|
command = ["mistral", "mistral-chat"];
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
title = "/mistral";
|
title = Environment.commandTitles.mistralChat;
|
||||||
description = "Chat with AI (Mistral)";
|
description = Environment.commandDescriptions.mistralChat;
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||||
console.log("match", match);
|
|
||||||
return this.executeMistral(msg, match?.[3] || "");
|
return this.executeMistral(msg, match?.[3] || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeMistral(msg: Message, text: string): Promise<void> {
|
async executeMistral(msg: Message, text: string, stream: boolean = true): Promise<void> {
|
||||||
if (!text || !text.trim().length) return;
|
await runUnifiedAi({provider: AiProvider.MISTRAL, msg, text, stream});
|
||||||
|
|
||||||
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 => {
|
|
||||||
const content = [];
|
|
||||||
content.push({
|
|
||||||
type: "text",
|
|
||||||
text: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const image of part.images) {
|
|
||||||
content.push({
|
|
||||||
type: "image_url",
|
|
||||||
imageUrl: "data:image/jpeg;base64," + image
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
role: part.bot ? "assistant" : "user",
|
|
||||||
content: content,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
chatMessages.reverse();
|
|
||||||
|
|
||||||
if (Environment.SYSTEM_PROMPT && Environment.USE_SYSTEM_PROMPT) {
|
|
||||||
chatMessages.unshift({role: "system", content: [{type: "text", text: Environment.SYSTEM_PROMPT}]});
|
|
||||||
}
|
|
||||||
|
|
||||||
let waitMessage: Message | null = null;
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const imagesCount = chatMessages.reduce((total, curr) => {
|
|
||||||
return total + (curr.content.filter(c => c.type === "image_url")?.length ?? 0);
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
if (imagesCount) {
|
|
||||||
try {
|
|
||||||
const modelInfo = await commands.find(c => c instanceof MistralGetModel)?.getModelCapabilities();
|
|
||||||
if (modelInfo) {
|
|
||||||
if (!modelInfo.vision?.supported) {
|
|
||||||
await replyToMessage({
|
|
||||||
message: msg,
|
|
||||||
text: "Моя текущая модель не умеет анализировать изображения 🥹"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
waitMessage = await bot.sendMessage({
|
|
||||||
chat_id: chatId,
|
|
||||||
text: imagesCount ?
|
|
||||||
imagesCount > 1 ? Environment.analyzingPicturesText : Environment.analyzingPictureText
|
|
||||||
: Environment.waitThinkText,
|
|
||||||
|
|
||||||
reply_parameters: {
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: msg.message_id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const stream = await mistralAi.chat.stream({
|
|
||||||
model: Environment.MISTRAL_MODEL,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
messages: chatMessages as any
|
|
||||||
});
|
|
||||||
|
|
||||||
let currentText = "";
|
|
||||||
let shouldBreak = false;
|
|
||||||
|
|
||||||
const editor = startIntervalEditor({
|
|
||||||
intervalMs: 4500,
|
|
||||||
getText: () => currentText,
|
|
||||||
editFn: async (text) => {
|
|
||||||
await bot.editMessageText(
|
|
||||||
{
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: <number>waitMessage?.message_id,
|
|
||||||
text: escapeMarkdownV2Text(text),
|
|
||||||
parse_mode: "MarkdownV2"
|
|
||||||
}
|
|
||||||
).catch(logError);
|
|
||||||
|
|
||||||
console.log("editMessageText", text);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
waitMessage.reply_to_message = msg;
|
|
||||||
waitMessage.text = text;
|
|
||||||
await MessageStore.put(waitMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onStop: async () => {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await editor.tick();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
console.log("chunk", chunk);
|
|
||||||
|
|
||||||
const text = chunk.data.choices[0].delta.content;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
if (Environment.SEND_TIME_TOOK) {
|
|
||||||
await replyToMessage({message: waitMessage, text: `⏱️ ${diff}s`});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
logError(e);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,15 @@
|
|||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {Requirements} from "../base/requirements";
|
|
||||||
import {Requirement} from "../base/requirement";
|
|
||||||
import {mistralAi} from "../index";
|
|
||||||
import {AiModelCapabilities} from "../model/ai-model-capabilities";
|
|
||||||
|
|
||||||
export class MistralGetModel extends Command {
|
export class MistralGetModel extends Command {
|
||||||
title = "/mistralGetModel";
|
title = Environment.commandTitles.mistralGetModel;
|
||||||
description = "Get current Mistral model";
|
description = Environment.commandDescriptions.mistralGetModel;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
await replyToMessage({message: msg, text: `Текущая модель: "${Environment.MISTRAL_MODEL}"`}).catch(logError);
|
await replyToMessage({message: msg, text: await formatRuntimeModelInfo(AiProvider.MISTRAL)}).catch(logError);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async getModelCapabilities(): Promise<AiModelCapabilities | null> {
|
|
||||||
try {
|
|
||||||
const info = await mistralAi.models.retrieve({modelId: Environment.MISTRAL_MODEL}) as any;
|
|
||||||
console.log(info);
|
|
||||||
|
|
||||||
return {
|
|
||||||
vision: {supported: info.capabilities.vision},
|
|
||||||
ocr: {supported: info.capabilities.ocr},
|
|
||||||
thinking: undefined,
|
|
||||||
tools: {supported: info.capabilities.functionCalling},
|
|
||||||
audio: {supported: info.capabilities.audioTranscription}
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,76 +2,27 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {mistralAi} from "../index";
|
import {escapeHtml, logError, replyToMessage} from "../util/utils";
|
||||||
import {logError, oldReplyToMessage, replyToMessage} from "../util/utils";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {listProviderModels} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class MistralListModels extends Command {
|
export class MistralListModels extends Command {
|
||||||
title = "/mistralListModels";
|
title = Environment.commandTitles.mistralListModels;
|
||||||
description = "List all Mistral models";
|
description = Environment.commandDescriptions.mistralListModels;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const listResponse = await mistralAi.models.list() as {
|
const models = (await listProviderModels(AiProvider.MISTRAL)).sort((a, b) => a.localeCompare(b));
|
||||||
object: string;
|
const modelsString = escapeHtml(models.join("\n").substring(0, 4000));
|
||||||
data: Array<BaseModelCard>
|
const text = Environment.modelListHeaderText + "<blockquote expandable>" + modelsString + "</blockquote>";
|
||||||
};
|
|
||||||
console.log(listResponse);
|
|
||||||
|
|
||||||
const modelsString = listResponse.data
|
await replyToMessage({message: msg, text, parse_mode: "HTML"});
|
||||||
.sort((a, b) => a?.name?.localeCompare(b.name || "") || -1)
|
|
||||||
.map(e => `${e.id}`)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const text = "Доступные модели:\n\n" + "<blockquote expandable>" + modelsString + "</blockquote>";
|
|
||||||
|
|
||||||
await replyToMessage({
|
|
||||||
message: msg,
|
|
||||||
text: text,
|
|
||||||
parse_mode: "HTML"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e);
|
logError(e);
|
||||||
await oldReplyToMessage(msg, "Не получилось загрузить список моделей").catch(logError);
|
await replyToMessage({message: msg, text: Environment.modelListLoadFailedText}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseModelCard = {
|
|
||||||
id: string;
|
|
||||||
object: string;
|
|
||||||
created?: number | undefined;
|
|
||||||
ownedBy: string;
|
|
||||||
/**
|
|
||||||
* This is populated by Harmattan, but some fields have a name
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* that we don't want to expose in the API.
|
|
||||||
*/
|
|
||||||
capabilities: ModelCapabilities;
|
|
||||||
name?: string | null | undefined;
|
|
||||||
description?: string | null | undefined;
|
|
||||||
maxContextLength: number;
|
|
||||||
aliases?: Array<string> | undefined;
|
|
||||||
deprecation?: Date | null | undefined;
|
|
||||||
deprecationReplacementModel?: string | null | undefined;
|
|
||||||
defaultModelTemperature?: number | null | undefined;
|
|
||||||
type: "base";
|
|
||||||
};
|
|
||||||
|
|
||||||
type ModelCapabilities = {
|
|
||||||
completionChat: boolean;
|
|
||||||
functionCalling: boolean;
|
|
||||||
reasoning: boolean;
|
|
||||||
completionFim: boolean;
|
|
||||||
fineTuning: boolean;
|
|
||||||
vision: boolean;
|
|
||||||
ocr: boolean;
|
|
||||||
classification: boolean;
|
|
||||||
moderation: boolean;
|
|
||||||
audio: boolean;
|
|
||||||
audioTranscription: boolean;
|
|
||||||
audioTranscriptionRealtime: boolean;
|
|
||||||
audioSpeech: boolean;
|
|
||||||
};
|
|
||||||
@@ -2,24 +2,28 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {getRuntimeModel, setRuntimeModel, formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class MistralSetModel extends Command {
|
export class MistralSetModel extends Command {
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
title = "/mistralSetModel";
|
title = Environment.commandTitles.mistralSetModel;
|
||||||
description = "Set Mistral model";
|
description = Environment.commandDescriptions.mistralSetModel;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
||||||
const newModel = match?.[3];
|
const newModel = match?.[3]?.trim();
|
||||||
Environment.setMistralModel(newModel || Environment.MISTRAL_MODEL);
|
if (newModel) setRuntimeModel(AiProvider.MISTRAL, newModel);
|
||||||
|
|
||||||
const text = newModel ? `Выбрана модель "${newModel}"`
|
const model = getRuntimeModel(AiProvider.MISTRAL);
|
||||||
: `Модель не задана. Будет использоваться стандартная модель "${Environment.MISTRAL_MODEL}".`;
|
const text = newModel
|
||||||
|
? Environment.getSelectedModelWithInfoText(model, await formatRuntimeModelInfo(AiProvider.MISTRAL))
|
||||||
|
: Environment.getModelIsNotSetCurrentText(model);
|
||||||
|
|
||||||
await replyToMessage({message: msg, text: text}).catch(logError);
|
await replyToMessage({message: msg, text}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2203
File diff suppressed because it is too large
Load Diff
@@ -1,121 +1,15 @@
|
|||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {boolToEmoji, logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {ollama} from "../index";
|
|
||||||
import {AiModelCapabilities} from "../model/ai-model-capabilities";
|
|
||||||
|
|
||||||
export class OllamaGetModel extends Command {
|
export class OllamaGetModel extends Command {
|
||||||
title = "/ollamaGetModel";
|
title = Environment.commandTitles.ollamaGetModel;
|
||||||
description = "Ollama model info";
|
description = Environment.commandDescriptions.ollamaGetModel;
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
try {
|
await replyToMessage({message: msg, text: await formatRuntimeModelInfo(AiProvider.OLLAMA)}).catch(logError);
|
||||||
const model = Environment.OLLAMA_MODEL;
|
|
||||||
const imageModel = Environment.OLLAMA_IMAGE_MODEL;
|
|
||||||
const thinkModel = Environment.OLLAMA_THINK_MODEL;
|
|
||||||
|
|
||||||
const promises: (Promise<AiModelCapabilities | null> | null)[] = [this.getModelCapabilities()];
|
|
||||||
|
|
||||||
if (imageModel && imageModel !== model) {
|
|
||||||
promises.push(this.loadImageModelInfo());
|
|
||||||
} else {
|
|
||||||
promises.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thinkModel && thinkModel !== model) {
|
|
||||||
promises.push(this.loadThinkModelInfo());
|
|
||||||
} else {
|
|
||||||
promises.push(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const infos = await Promise.all(promises);
|
|
||||||
|
|
||||||
let modelInfo = infos[0];
|
|
||||||
const modelText = "```Text\n" + this.getModelText(model, modelInfo) + "```";
|
|
||||||
|
|
||||||
modelInfo = infos[1];
|
|
||||||
const imageModelText = modelInfo ?
|
|
||||||
"```Image\n" + this.getModelText(imageModel, modelInfo) + "```" : null;
|
|
||||||
|
|
||||||
modelInfo = infos[2];
|
|
||||||
const thinkModelText = modelInfo ?
|
|
||||||
"```Think\n" + this.getModelText(thinkModel, modelInfo) + "```" : null;
|
|
||||||
|
|
||||||
const modelInfos = [modelText];
|
|
||||||
if (imageModelText) {
|
|
||||||
modelInfos.push(imageModelText);
|
|
||||||
}
|
|
||||||
if (thinkModelText) {
|
|
||||||
modelInfos.push(thinkModelText);
|
|
||||||
}
|
|
||||||
|
|
||||||
await replyToMessage({
|
|
||||||
message: msg,
|
|
||||||
text: modelInfos.join("\n\n"),
|
|
||||||
parse_mode: "Markdown"
|
|
||||||
}).catch(logError);
|
|
||||||
|
|
||||||
} catch (e: any) {
|
|
||||||
logError(e);
|
|
||||||
await replyToMessage({message: msg, text: e.toString()}).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private getModelText(model: string | undefined, info: AiModelCapabilities | null): string {
|
|
||||||
return `model: ${model}\n\n` +
|
|
||||||
`vision: ${boolToEmoji(info?.vision?.supported)}\n` +
|
|
||||||
`ocr: ${boolToEmoji(info?.ocr?.supported)}\n` +
|
|
||||||
`thinking: ${boolToEmoji(info?.thinking?.supported)}\n` +
|
|
||||||
`tools: ${boolToEmoji(info?.tools?.supported)}\n` +
|
|
||||||
`audio: ${boolToEmoji(info?.audio?.supported)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getModelCapabilities(model: string | undefined = Environment.OLLAMA_MODEL): Promise<AiModelCapabilities | null> {
|
|
||||||
if (!model) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const info = await ollama.show({model: model});
|
|
||||||
console.log(info);
|
|
||||||
|
|
||||||
return {
|
|
||||||
vision: {
|
|
||||||
supported: info.capabilities.includes("vision"),
|
|
||||||
external: model !== Environment.OLLAMA_MODEL,
|
|
||||||
model: model
|
|
||||||
},
|
|
||||||
ocr: {
|
|
||||||
supported: info.capabilities.includes("ocr"),
|
|
||||||
external: model !== Environment.OLLAMA_MODEL,
|
|
||||||
model: model
|
|
||||||
},
|
|
||||||
thinking: {
|
|
||||||
supported: info.capabilities.includes("thinking"),
|
|
||||||
external: model !== Environment.OLLAMA_MODEL,
|
|
||||||
model: model
|
|
||||||
},
|
|
||||||
tools: {
|
|
||||||
supported: info.capabilities.includes("tools"),
|
|
||||||
external: model !== Environment.OLLAMA_MODEL,
|
|
||||||
model: model
|
|
||||||
},
|
|
||||||
audio: {
|
|
||||||
supported: info.capabilities.includes("audio"),
|
|
||||||
external: model !== Environment.OLLAMA_MODEL,
|
|
||||||
model: model
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadImageModelInfo(): Promise<AiModelCapabilities | null> {
|
|
||||||
return this.getModelCapabilities(Environment.OLLAMA_IMAGE_MODEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadThinkModelInfo(): Promise<AiModelCapabilities | null> {
|
|
||||||
return this.getModelCapabilities(Environment.OLLAMA_THINK_MODEL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
|
||||||
import {ollama} from "../index";
|
|
||||||
import {logError, oldReplyToMessage, replyToMessage} from "../util/utils";
|
|
||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
import {escapeHtml, logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {listProviderModels} from "../ai/provider-model-runtime";
|
||||||
|
import {createOllamaClient, resolveAiRuntimeTarget} from "../ai/ai-runtime-target";
|
||||||
|
|
||||||
export class OllamaListModels extends Command {
|
export class OllamaListModels extends Command {
|
||||||
title = "/ollamaListModels";
|
title = Environment.commandTitles.ollamaListModels;
|
||||||
description = "List all Ollama models";
|
description = Environment.commandDescriptions.ollamaListModels;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const listResponse = await ollama.list();
|
const target = resolveAiRuntimeTarget(AiProvider.OLLAMA, "chat");
|
||||||
console.log(listResponse);
|
const models = (await listProviderModels(AiProvider.OLLAMA)).sort((a, b) => a.localeCompare(b));
|
||||||
|
const modelsString = escapeHtml(models.join("\n").substring(0, 4000));
|
||||||
|
const loadedModels = ((await createOllamaClient(target).ps())?.models ?? [])
|
||||||
|
.map(model => model.model || model.name)
|
||||||
|
.filter((model): model is string => !!model);
|
||||||
|
const text =
|
||||||
|
Environment.getLoadedModelsText(loadedModels) + "\n\n" +
|
||||||
|
Environment.modelListHeaderText + "<blockquote expandable>" + modelsString + "</blockquote>";
|
||||||
|
|
||||||
const modelsString = listResponse.models
|
await replyToMessage({message: msg, text, parse_mode: "HTML"});
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
|
||||||
.map(e => `${e.model}`)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const text = "Доступные модели:\n\n" + "<blockquote expandable>" + modelsString + "</blockquote>";
|
|
||||||
|
|
||||||
await replyToMessage({
|
|
||||||
message: msg,
|
|
||||||
text: text,
|
|
||||||
parse_mode: "HTML"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e);
|
logError(e);
|
||||||
await oldReplyToMessage(msg, "Не получилось загрузить список моделей").catch(logError);
|
await replyToMessage({message: msg, text: Environment.modelListLoadFailedText}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
import {Command} from "../base/command";
|
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
|
||||||
import {abortOllamaRequest, bot, getOllamaRequest, ollama, ollamaRequests} from "../index";
|
|
||||||
import {escapeMarkdownV2Text, logError, oldReplyToMessage, startIntervalEditor} from "../util/utils";
|
|
||||||
import {Requirements} from "../base/requirements";
|
|
||||||
import {Requirement} from "../base/requirement";
|
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {Cancel} from "../callback_commands/cancel";
|
|
||||||
import {OllamaCancel} from "../callback_commands/ollama-cancel";
|
|
||||||
import {MessageStore} from "../common/message-store";
|
|
||||||
|
|
||||||
export class OllamaPrompt extends Command {
|
|
||||||
command = "ollamaPrompt";
|
|
||||||
argsMode = "required" as const;
|
|
||||||
|
|
||||||
title = "/ollamaPrompt";
|
|
||||||
description = "Custom prompt for AI (Ollama)";
|
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_ADMIN);
|
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
|
||||||
console.log("match", match);
|
|
||||||
return this.executeOllama(msg, match?.[3] || "");
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeOllama(msg: Message, text: string): Promise<void> {
|
|
||||||
if (!text || !text.trim().length) return;
|
|
||||||
if (!msg.from) return;
|
|
||||||
|
|
||||||
const chatId = msg.chat.id;
|
|
||||||
|
|
||||||
let waitMessage: Message | null = null;
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const uuid = crypto.randomUUID();
|
|
||||||
const cancelMarkup = {inline_keyboard: [[Cancel.withData(new OllamaCancel().data + " " + uuid).asButton()]]};
|
|
||||||
|
|
||||||
waitMessage = await bot.sendMessage({
|
|
||||||
chat_id: chatId,
|
|
||||||
text: Environment.waitThinkText,
|
|
||||||
reply_parameters: {
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: msg.message_id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const stream = await ollama.generate({
|
|
||||||
model: <string>Environment.OLLAMA_MODEL,
|
|
||||||
stream: true,
|
|
||||||
think: false,
|
|
||||||
prompt: text
|
|
||||||
});
|
|
||||||
|
|
||||||
const newRequest = {
|
|
||||||
uuid: uuid,
|
|
||||||
stream: stream,
|
|
||||||
done: false,
|
|
||||||
fromId: msg.from.id,
|
|
||||||
chatId: msg.chat.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("Pushing new request", newRequest);
|
|
||||||
ollamaRequests.push(newRequest);
|
|
||||||
|
|
||||||
await bot.editMessageReplyMarkup(
|
|
||||||
{
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: waitMessage.message_id,
|
|
||||||
reply_markup: cancelMarkup
|
|
||||||
}
|
|
||||||
).catch(logError);
|
|
||||||
|
|
||||||
let currentText = "";
|
|
||||||
let shouldBreak = false;
|
|
||||||
|
|
||||||
const editor = startIntervalEditor({
|
|
||||||
uuid: uuid,
|
|
||||||
intervalMs: 4500,
|
|
||||||
getText: () => currentText,
|
|
||||||
editFn: async (text) => {
|
|
||||||
if (getOllamaRequest(uuid)?.done) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await bot.editMessageText({
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: <number>waitMessage?.message_id,
|
|
||||||
text: escapeMarkdownV2Text(text),
|
|
||||||
parse_mode: "Markdown",
|
|
||||||
reply_markup: cancelMarkup
|
|
||||||
}).catch(logError);
|
|
||||||
|
|
||||||
console.log("editMessageText", text);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
waitMessage.reply_to_message = msg;
|
|
||||||
waitMessage.text = text;
|
|
||||||
await MessageStore.put(waitMessage);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await editor.tick();
|
|
||||||
|
|
||||||
try {
|
|
||||||
let isThinking = false;
|
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
|
|
||||||
const content = chunk.response;
|
|
||||||
|
|
||||||
if (content === "<think>" || chunk.thinking) {
|
|
||||||
if (!isThinking) {
|
|
||||||
await bot.editMessageText({
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: waitMessage.message_id,
|
|
||||||
text: "🤔 Размышляю...",
|
|
||||||
parse_mode: "Markdown",
|
|
||||||
}).catch(logError);
|
|
||||||
}
|
|
||||||
|
|
||||||
isThinking = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isThinking) {
|
|
||||||
currentText += content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isThinking && !chunk.thinking) {
|
|
||||||
currentText += content;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content === "</think>" || !chunk.thinking) {
|
|
||||||
isThinking = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentText.length > 4096) {
|
|
||||||
currentText = currentText.slice(0, 4093) + "...";
|
|
||||||
shouldBreak = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getOllamaRequest(uuid)?.done) {
|
|
||||||
shouldBreak = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldBreak || chunk.done) {
|
|
||||||
console.log("messageText", currentText);
|
|
||||||
console.log("length", currentText.length);
|
|
||||||
|
|
||||||
if (shouldBreak) {
|
|
||||||
console.log("break", true);
|
|
||||||
} else {
|
|
||||||
console.log("ended", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const diff = Math.abs(Date.now() - startTime) / 1000;
|
|
||||||
|
|
||||||
await editor.tick();
|
|
||||||
await editor.stop();
|
|
||||||
|
|
||||||
console.log(`aborted request ${uuid}:`, abortOllamaRequest(uuid));
|
|
||||||
|
|
||||||
waitMessage.reply_to_message = msg;
|
|
||||||
waitMessage.text = currentText;
|
|
||||||
await MessageStore.put(waitMessage);
|
|
||||||
await oldReplyToMessage(waitMessage, `⏱️ ${diff}s`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await bot.editMessageReplyMarkup({
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: waitMessage.message_id,
|
|
||||||
reply_markup: {inline_keyboard: []}
|
|
||||||
}).catch(logError);
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
if (e.message.toLowerCase().includes("aborted")) return;
|
|
||||||
logError(e);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
await bot.editMessageReplyMarkup({
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: waitMessage.message_id,
|
|
||||||
reply_markup: {inline_keyboard: []}
|
|
||||||
}).catch(logError);
|
|
||||||
|
|
||||||
await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${e.toString()}`).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,52 +2,39 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {bot, ollama} from "../index";
|
import {escapeHtml, logError, replyToMessage} from "../util/utils";
|
||||||
import {WebSearchResponse} from "../model/web-search-response";
|
|
||||||
import {logError, oldEditMessageText} from "../util/utils";
|
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
|
import {createOllamaClient, resolveAiRuntimeTarget} from "../ai/ai-runtime-target";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
|
||||||
export class OllamaSearch extends Command {
|
export class OllamaSearch extends Command {
|
||||||
command = ["s", "search"];
|
command = ["s", "search"];
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
title = "/search";
|
title = Environment.commandTitles.ollamaSearch;
|
||||||
description = "Web search via Ollama";
|
description = Environment.commandDescriptions.ollamaSearch;
|
||||||
|
|
||||||
override requirements = Requirements.Build(Requirement.BOT_ADMIN);
|
override requirements = Requirements.Build(Requirement.BOT_ADMIN);
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
||||||
console.log("match", match);
|
|
||||||
|
|
||||||
const query = match?.[3] || "";
|
const query = match?.[3] || "";
|
||||||
if (!query || !query.length) return;
|
if (!query || !query.length) return;
|
||||||
|
|
||||||
const chatId = msg.chat.id;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wait = await bot.sendMessage({
|
const target = resolveAiRuntimeTarget(AiProvider.OLLAMA, "chat");
|
||||||
chat_id: chatId,
|
const result = await createOllamaClient(target).webSearch({query, maxResults: 10});
|
||||||
text: Environment.waitThinkText,
|
const body = (result.results ?? [])
|
||||||
reply_parameters: {
|
.map((item, index) => `${index + 1}. ${item.content}`)
|
||||||
chat_id: chatId,
|
.join("\n\n");
|
||||||
message_id: msg.message_id
|
|
||||||
},
|
await replyToMessage({
|
||||||
parse_mode: "Markdown"
|
message: msg,
|
||||||
|
text: Environment.searchResultsHeaderText + "<blockquote expandable>" + escapeHtml(body) + "</blockquote>",
|
||||||
|
parse_mode: "HTML",
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await ollama.webSearch({query: query});
|
|
||||||
console.log("results", results);
|
|
||||||
|
|
||||||
let message = "Результаты:\n\n";
|
|
||||||
results.results.forEach((result, index) => {
|
|
||||||
const r = result as WebSearchResponse;
|
|
||||||
message += `${index + 1}. ${r.url}\n`;
|
|
||||||
});
|
|
||||||
|
|
||||||
await oldEditMessageText(chatId, wait.message_id, message);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error);
|
logError(error);
|
||||||
|
await replyToMessage({message: msg, text: Environment.errorText}).catch(logError);
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,29 @@
|
|||||||
import {Message} from "typescript-telegram-bot-api";
|
|
||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
|
||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {ollama} from "../index";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {getRuntimeModel, setRuntimeModel, formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class OllamaSetModel extends Command {
|
export class OllamaSetModel extends Command {
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
title = "/ollamaSetModel";
|
title = Environment.commandTitles.ollamaSetModel;
|
||||||
description = "Set Ollama model";
|
description = Environment.commandDescriptions.ollamaSetModel;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
||||||
const newModel = match?.[3] || "";
|
const newModel = match?.[3]?.trim();
|
||||||
if (!newModel || !newModel.length) return;
|
if (newModel) setRuntimeModel(AiProvider.OLLAMA, newModel);
|
||||||
|
|
||||||
try {
|
const model = getRuntimeModel(AiProvider.OLLAMA);
|
||||||
await ollama.show({model: newModel});
|
const text = newModel
|
||||||
|
? Environment.getSelectedModelWithInfoText(model, await formatRuntimeModelInfo(AiProvider.OLLAMA))
|
||||||
|
: Environment.getModelIsNotSetCurrentText(model);
|
||||||
|
|
||||||
Environment.setOllamaModel(newModel || <string>Environment.OLLAMA_MODEL);
|
await replyToMessage({message: msg, text}).catch(logError);
|
||||||
|
|
||||||
const text = newModel ? `Выбрана модель "${newModel}"`
|
|
||||||
: `Модель не задана. Будет использоваться стандартная модель "${Environment.OLLAMA_MODEL}".`;
|
|
||||||
|
|
||||||
await replyToMessage({message: msg, text: text}).catch(logError);
|
|
||||||
} catch (e: any) {
|
|
||||||
logError(e);
|
|
||||||
await replyToMessage({message: msg, text: e.toString()}).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-156
@@ -1,17 +1,10 @@
|
|||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {MessageStore} from "../common/message-store";
|
import {ChatCommand} from "../base/chat-command";
|
||||||
import {
|
|
||||||
collectReplyChainText,
|
|
||||||
escapeMarkdownV2Text,
|
|
||||||
logError,
|
|
||||||
replyToMessage,
|
|
||||||
startIntervalEditor
|
|
||||||
} from "../util/utils";
|
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {bot, openAi} from "../index";
|
|
||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {ChatCommand} from "../base/chat-command";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {runUnifiedAi} from "../ai/unified-ai-runner";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class OpenAIChat extends ChatCommand {
|
export class OpenAIChat extends ChatCommand {
|
||||||
command = ["openai", "chatgpt"];
|
command = ["openai", "chatgpt"];
|
||||||
@@ -19,154 +12,14 @@ export class OpenAIChat extends ChatCommand {
|
|||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
title = "/openAI";
|
title = Environment.commandTitles.openAiChat;
|
||||||
description = "Chat with AI (OpenAI)";
|
description = Environment.commandDescriptions.openAiChat;
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||||
console.log("OpenAI Chat: ", match);
|
|
||||||
return this.executeOpenAI(msg, match?.[3] || "");
|
return this.executeOpenAI(msg, match?.[3] || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeOpenAI(msg: Message, text: string): Promise<void> {
|
async executeOpenAI(msg: Message, text: string, stream: boolean = true): Promise<void> {
|
||||||
if (!text || !text.trim().length) return;
|
await runUnifiedAi({provider: AiProvider.OPENAI, msg, text, stream});
|
||||||
|
|
||||||
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 => {
|
|
||||||
const content = [];
|
|
||||||
content.push({
|
|
||||||
type: part.bot ? "output_text" : "input_text",
|
|
||||||
text: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: 03/02/2026, Danil Nikolaev: upload file then add here
|
|
||||||
// for (const image of part.images) {
|
|
||||||
// content.push({
|
|
||||||
// type: "image_url",
|
|
||||||
// imageUrl: "data:image/jpeg;base64," + image
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
|
||||||
role: part.bot ? "assistant" : "user",
|
|
||||||
content: content,
|
|
||||||
type: "message",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
chatMessages.reverse();
|
|
||||||
|
|
||||||
if (Environment.SYSTEM_PROMPT && Environment.USE_SYSTEM_PROMPT) {
|
|
||||||
chatMessages.unshift({
|
|
||||||
role: "system",
|
|
||||||
content: [{type: "input_text", text: Environment.SYSTEM_PROMPT}],
|
|
||||||
type: "message"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let waitMessage: Message | null = null;
|
|
||||||
|
|
||||||
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 openAi.responses.create({
|
|
||||||
model: Environment.OPENAI_MODEL,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
input: chatMessages as any,
|
|
||||||
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: <number>waitMessage?.message_id,
|
|
||||||
text: escapeMarkdownV2Text(text),
|
|
||||||
parse_mode: "MarkdownV2"
|
|
||||||
}
|
|
||||||
).catch(logError);
|
|
||||||
|
|
||||||
console.log("editMessageText", text);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
waitMessage.reply_to_message = msg;
|
|
||||||
waitMessage.text = text;
|
|
||||||
await MessageStore.put(waitMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onStop: async () => {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await editor.tick();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
console.log("chunk", chunk);
|
|
||||||
|
|
||||||
if (chunk.type === "response.output_text.delta") {
|
|
||||||
const text = chunk.delta;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
|
|
||||||
if (Environment.SEND_TIME_TOOK) {
|
|
||||||
await replyToMessage({message: waitMessage, text: `⏱️ ${diff}s`});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
logError(e);
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
await replyToMessage({
|
|
||||||
message: waitMessage,
|
|
||||||
text: `Произошла ошибка!\n${e.toString()}`
|
|
||||||
}).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
import {ChatCommand} from "../base/chat-command";
|
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
|
||||||
import {Requirements} from "../base/requirements";
|
|
||||||
import {Requirement} from "../base/requirement";
|
|
||||||
import {bot, openAi, photoGenDir} from "../index";
|
|
||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import {oldEditMessageText, logError, replyToMessage} from "../util/utils";
|
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {APIError} from "openai";
|
|
||||||
|
|
||||||
export class OpenAIGenImage extends ChatCommand {
|
|
||||||
command = ["openAiGenImage", "chatGPTGenImage", "imgen"];
|
|
||||||
|
|
||||||
title = "/openAIGenImage";
|
|
||||||
description = "Generate image from OpenAI";
|
|
||||||
|
|
||||||
argsMode = "required" as const;
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
|
||||||
const prompt = match?.[3]?.trim();
|
|
||||||
if (!prompt?.length) return;
|
|
||||||
|
|
||||||
let waitMessage: Message | null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const totalParts = 3;
|
|
||||||
const model = Environment.OPENAI_IMAGE_MODEL;
|
|
||||||
const fileFullName = `${msg.chat.id}_${msg.message_id}.png`;
|
|
||||||
const getFileLocation = (fn: string) => {
|
|
||||||
return path.join(photoGenDir, fn);
|
|
||||||
};
|
|
||||||
|
|
||||||
waitMessage = await replyToMessage({message: msg, text: "🌈 Генерирую изображение..."});
|
|
||||||
|
|
||||||
const stream = await openAi.images.generate({
|
|
||||||
model: model,
|
|
||||||
prompt: prompt,
|
|
||||||
n: 1,
|
|
||||||
size: "auto",
|
|
||||||
stream: true,
|
|
||||||
partial_images: totalParts,
|
|
||||||
moderation: "low",
|
|
||||||
output_format: "png",
|
|
||||||
});
|
|
||||||
|
|
||||||
const then = Date.now();
|
|
||||||
|
|
||||||
for await (const event of stream) {
|
|
||||||
switch (event.type) {
|
|
||||||
case "image_generation.partial_image": {
|
|
||||||
console.log(` Partial image ${event.partial_image_index + 1}/3 received`);
|
|
||||||
console.log(` Size: ${event.b64_json.length} characters (base64)`);
|
|
||||||
|
|
||||||
const fileName = `partial_${event.partial_image_index + 1}_${fileFullName}`;
|
|
||||||
const imageBuffer = Buffer.from(event.b64_json, "base64");
|
|
||||||
const fileLocation = getFileLocation(fileName);
|
|
||||||
fs.writeFileSync(fileLocation, imageBuffer);
|
|
||||||
console.log(` 💾 Saved to: ${path.resolve(fileLocation)}`);
|
|
||||||
|
|
||||||
await bot.editMessageMedia({
|
|
||||||
chat_id: msg.chat.id,
|
|
||||||
message_id: waitMessage.message_id,
|
|
||||||
media: {
|
|
||||||
type: "photo",
|
|
||||||
media: imageBuffer,
|
|
||||||
caption: `🌈 Генерирую изображение (${(event.partial_image_index + 1)}/${totalParts})...`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "image_generation.completed": {
|
|
||||||
console.log("\n✅ Final image completed!");
|
|
||||||
console.log(` Size: ${event.b64_json.length} characters (base64)`);
|
|
||||||
|
|
||||||
const imageBuffer = Buffer.from(event.b64_json, "base64");
|
|
||||||
const fileLocation = getFileLocation(fileFullName);
|
|
||||||
fs.writeFileSync(fileLocation, imageBuffer);
|
|
||||||
console.log(` Saved to: ${path.resolve(fileLocation)}`);
|
|
||||||
|
|
||||||
const diff = Date.now() - then;
|
|
||||||
await bot.editMessageMedia({
|
|
||||||
chat_id: msg.chat.id,
|
|
||||||
message_id: waitMessage.message_id,
|
|
||||||
media: {
|
|
||||||
type: "photo",
|
|
||||||
media: imageBuffer,
|
|
||||||
caption: `🌈 Изображение по запросу "${prompt}" сгенерировано моделью "${model}" размеров ${event.size} за ${diff}ms`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
console.log(`❓ Unknown event: ${event}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
|
|
||||||
if (e instanceof APIError && e.error.code === "moderation_blocked") {
|
|
||||||
const text = "❌ Мне запрещено такое генерировать 😠";
|
|
||||||
|
|
||||||
if (waitMessage) {
|
|
||||||
await oldEditMessageText(msg.chat.id, waitMessage.message_id, text).catch(logError);
|
|
||||||
} else {
|
|
||||||
await replyToMessage({message: msg, text: text}).catch(logError);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await replyToMessage({
|
|
||||||
message: waitMessage ? waitMessage : msg,
|
|
||||||
text: `Произошла ошибка: ${e}`
|
|
||||||
}).catch(logError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,15 @@
|
|||||||
import {Command} from "../base/command";
|
import {Command} from "../base/command";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
import {Environment} from "../common/environment";
|
import {Environment} from "../common/environment";
|
||||||
import {AiModelCapabilities} from "../model/ai-model-capabilities";
|
|
||||||
|
|
||||||
export class OpenAIGetModel extends Command {
|
export class OpenAIGetModel extends Command {
|
||||||
title = "/openAIGetModel";
|
title = Environment.commandTitles.openAiGetModel;
|
||||||
description = "Get current OpenAI model";
|
description = Environment.commandDescriptions.openAiGetModel;
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
await replyToMessage({message: msg, text: `Текущая модель: "${Environment.OPENAI_MODEL}"`}).catch(logError);
|
await replyToMessage({message: msg, text: await formatRuntimeModelInfo(AiProvider.OPENAI)}).catch(logError);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
async getModelCapabilities(): Promise<AiModelCapabilities | null> {
|
|
||||||
// TODO: 12/02/2026, Danil Nikolaev: find solution
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
vision: {supported: true},
|
|
||||||
ocr: undefined,
|
|
||||||
thinking: {supported: true},
|
|
||||||
tools: {supported: true},
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,36 +2,27 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {openAi} from "../index";
|
import {escapeHtml, logError, replyToMessage} from "../util/utils";
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {listProviderModels} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class OpenAIListModels extends Command {
|
export class OpenAIListModels extends Command {
|
||||||
title = "/openAIListModels";
|
title = Environment.commandTitles.openAiListModels;
|
||||||
description = "List all OpenAI models";
|
description = Environment.commandDescriptions.openAiListModels;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message): Promise<void> {
|
async execute(msg: Message): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const listResponse = await openAi.models.list();
|
const models = (await listProviderModels(AiProvider.OPENAI)).sort((a, b) => a.localeCompare(b));
|
||||||
console.log(listResponse);
|
const modelsString = escapeHtml(models.join("\n").substring(0, 4000));
|
||||||
|
const text = Environment.modelListHeaderText + "<blockquote expandable>" + modelsString + "</blockquote>";
|
||||||
|
|
||||||
const modelsString = listResponse.data
|
await replyToMessage({message: msg, text, parse_mode: "HTML"});
|
||||||
.map(e => `${e.id}`)
|
|
||||||
.sort((a, b) => a.localeCompare(b))
|
|
||||||
.join("\n")
|
|
||||||
.substring(0, 4000);
|
|
||||||
|
|
||||||
const text = "Доступные модели:\n\n" + "<blockquote expandable>" + modelsString + "</blockquote>";
|
|
||||||
|
|
||||||
await replyToMessage({
|
|
||||||
message: msg,
|
|
||||||
text: text,
|
|
||||||
parse_mode: "HTML"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e);
|
logError(e);
|
||||||
await replyToMessage({message: msg, text: "Не получилось загрузить список моделей"}).catch(logError);
|
await replyToMessage({message: msg, text: Environment.modelListLoadFailedText}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,28 @@ import {Command} from "../base/command";
|
|||||||
import {Requirements} from "../base/requirements";
|
import {Requirements} from "../base/requirements";
|
||||||
import {Requirement} from "../base/requirement";
|
import {Requirement} from "../base/requirement";
|
||||||
import {Message} from "typescript-telegram-bot-api";
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
import {Environment} from "../common/environment";
|
|
||||||
import {logError, replyToMessage} from "../util/utils";
|
import {logError, replyToMessage} from "../util/utils";
|
||||||
|
import {AiProvider} from "../model/ai-provider";
|
||||||
|
import {getRuntimeModel, setRuntimeModel, formatRuntimeModelInfo} from "../ai/provider-model-runtime";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
|
||||||
export class OpenAISetModel extends Command {
|
export class OpenAISetModel extends Command {
|
||||||
argsMode = "required" as const;
|
argsMode = "required" as const;
|
||||||
|
|
||||||
title = "/openAISetModel";
|
title = Environment.commandTitles.openAiSetModel;
|
||||||
description = "Set OpenAI model";
|
description = Environment.commandDescriptions.openAiSetModel;
|
||||||
|
|
||||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
async execute(msg: Message, match?: RegExpExecArray | null): Promise<void> {
|
||||||
const newModel = match?.[3];
|
const newModel = match?.[3]?.trim();
|
||||||
Environment.setOpenAIModel(newModel || Environment.OPENAI_MODEL);
|
if (newModel) setRuntimeModel(AiProvider.OPENAI, newModel);
|
||||||
|
|
||||||
const text = newModel ? `Выбрана модель "${newModel}"`
|
const model = getRuntimeModel(AiProvider.OPENAI);
|
||||||
: `Модель не задана. Будет использоваться стандартная модель "${Environment.OPENAI_MODEL}".`;
|
const text = newModel
|
||||||
|
? Environment.getSelectedModelWithInfoText(model, await formatRuntimeModelInfo(AiProvider.OPENAI))
|
||||||
|
: Environment.getModelIsNotSetCurrentText(model);
|
||||||
|
|
||||||
await replyToMessage({message: msg, text: text}).catch(logError);
|
await replyToMessage({message: msg, text}).catch(logError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user