feat(ai): add configurable default provider + refactor telegram handlers
- add AiProvider enum and DEFAULT_AI_PROVIDER env var (fallback: OLLAMA) - route chat execution based on selected provider (Ollama/Gemini/Mistral/OpenAI) - move inline query / callback / edited message / my_chat_member handlers into utils - minor cleanup (command requirements placement, whitespace)
This commit is contained in:
@@ -18,11 +18,11 @@ export class GeminiChat extends ChatCommand {
|
||||
command = "gemini";
|
||||
argsMode = "required" as const;
|
||||
|
||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||
|
||||
title = "/gemini";
|
||||
description = "Chat with AI (Gemini)";
|
||||
|
||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||
|
||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||
console.log("match", match);
|
||||
return this.executeGemini(msg, match?.[3]);
|
||||
|
||||
@@ -17,11 +17,11 @@ export class MistralChat extends ChatCommand {
|
||||
command = "mistral";
|
||||
argsMode = "required" as const;
|
||||
|
||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||
|
||||
title = "/mistral";
|
||||
description = "Chat with AI (Mistral)";
|
||||
|
||||
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||
|
||||
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||
console.log("match", match);
|
||||
return this.executeMistral(msg, match?.[3]);
|
||||
|
||||
@@ -133,8 +133,6 @@ export class OpenAIChat extends ChatCommand {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} finally {
|
||||
await editor.tick();
|
||||
|
||||
@@ -3,6 +3,13 @@ import {saveData} from "../db/database";
|
||||
import {Answers} from "../model/answers";
|
||||
import {ifTrue} from "../util/utils";
|
||||
|
||||
export enum AiProvider {
|
||||
OLLAMA = "OLLAMA",
|
||||
GEMINI = "GEMINI",
|
||||
MISTRAL = "MISTRAL",
|
||||
OPENAI = "OPENAI",
|
||||
}
|
||||
|
||||
export class Environment {
|
||||
static BOT_TOKEN: string;
|
||||
static TEST_ENVIRONMENT: boolean;
|
||||
@@ -24,6 +31,8 @@ export class Environment {
|
||||
|
||||
static MAX_PHOTO_SIZE: number;
|
||||
|
||||
static DEFAULT_AI_PROVIDER: AiProvider;
|
||||
|
||||
static SYSTEM_PROMPT?: string;
|
||||
|
||||
static OLLAMA_ADDRESS?: string;
|
||||
@@ -65,6 +74,13 @@ export class Environment {
|
||||
|
||||
Environment.MAX_PHOTO_SIZE = Number(process.env.MAX_PHOTO_SIZE || "1280");
|
||||
|
||||
const aiProvider = process.env.DEFAULT_AI_PROVIDER || "OLLAMA";
|
||||
if (Object.values(AiProvider).includes(aiProvider as AiProvider)) {
|
||||
Environment.DEFAULT_AI_PROVIDER = aiProvider as AiProvider;
|
||||
} else {
|
||||
Environment.DEFAULT_AI_PROVIDER = AiProvider.OLLAMA;
|
||||
}
|
||||
|
||||
Environment.SYSTEM_PROMPT = process.env.SYSTEM_PROMPT?.trim();
|
||||
|
||||
Environment.OLLAMA_ADDRESS = process.env.OLLAMA_ADDRESS;
|
||||
|
||||
+11
-74
@@ -1,14 +1,16 @@
|
||||
import "dotenv/config";
|
||||
import {Environment} from "./common/environment";
|
||||
import {InlineQueryResult, TelegramBot, User} from "typescript-telegram-bot-api";
|
||||
import {TelegramBot, User} from "typescript-telegram-bot-api";
|
||||
import {Command} from "./base/command";
|
||||
import {
|
||||
delay,
|
||||
extractTextMessage,
|
||||
findAndExecuteCallbackCommand,
|
||||
ignore,
|
||||
initSystemSpecs,
|
||||
logError,
|
||||
processCallbackQuery,
|
||||
processEditedMessage,
|
||||
processInlineQuery,
|
||||
processMyChatMember,
|
||||
processNewMessage
|
||||
} from "./util/utils";
|
||||
import {Ae} from "./commands/ae";
|
||||
@@ -27,7 +29,6 @@ import {RandomInt} from "./commands/random-int";
|
||||
import {Ban} from "./commands/ban";
|
||||
import {Quote} from "./commands/quote";
|
||||
import {Ollama} from "ollama";
|
||||
import {WebSearchResponse} from "./model/web-search-response";
|
||||
import {OllamaSearch} from "./commands/ollama-search";
|
||||
import {Id} from "./commands/id";
|
||||
import {OllamaPrompt} from "./commands/ollama-prompt";
|
||||
@@ -37,7 +38,6 @@ import {Shutdown} from "./commands/shutdown";
|
||||
import {Leave} from "./commands/leave";
|
||||
import {OllamaChat} from "./commands/ollama-chat";
|
||||
import {Start} from "./commands/start";
|
||||
import {MessageStore} from "./common/message-store";
|
||||
import {GeminiChat} from "./commands/gemini-chat";
|
||||
import {Choice} from "./commands/choice";
|
||||
import {Coin} from "./commands/coin";
|
||||
@@ -221,7 +221,8 @@ async function main() {
|
||||
`TEST_ENVIRONMENT: ${Environment.TEST_ENVIRONMENT}\n` +
|
||||
`DATA_PATH: ${Environment.DATA_PATH}\n` +
|
||||
`MAX_PHOTO_SIZE: ${Environment.MAX_PHOTO_SIZE}\n` +
|
||||
`ONLY_FOR_CREATOR: ${Environment.ONLY_FOR_CREATOR_MODE}`
|
||||
`ONLY_FOR_CREATOR: ${Environment.ONLY_FOR_CREATOR_MODE}\n` +
|
||||
`DEFAULT_AI_PROVIDER: ${Environment.DEFAULT_AI_PROVIDER}`
|
||||
);
|
||||
|
||||
fs.mkdir(photoDir, ignore);
|
||||
@@ -272,74 +273,10 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
bot.on("my_chat_member", async (u) => {
|
||||
console.log("my_chat_member", u);
|
||||
});
|
||||
|
||||
bot.on("edited_message", async (msg) => {
|
||||
console.log("edited_message", msg);
|
||||
|
||||
await UserStore.put(msg.from);
|
||||
|
||||
if (!extractTextMessage(msg) || msg.from.id === botUser.id) return;
|
||||
|
||||
await MessageStore.put(msg);
|
||||
});
|
||||
|
||||
bot.on("my_chat_member", processMyChatMember);
|
||||
bot.on("edited_message", processEditedMessage);
|
||||
bot.on("message", processNewMessage);
|
||||
|
||||
bot.on("inline_query", async (query) => {
|
||||
console.log("query", query);
|
||||
|
||||
if (Environment.CREATOR_ID !== query.from.id) {
|
||||
await bot.answerInlineQuery({
|
||||
inline_query_id: query.id,
|
||||
results: [],
|
||||
button: {
|
||||
text: "No access",
|
||||
start_parameter: "nope"
|
||||
}
|
||||
}).catch(logError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.query.trim().length !== 0) {
|
||||
try {
|
||||
const queryResults: InlineQueryResult[] = [];
|
||||
const results = await ollama.webSearch({query: query.query});
|
||||
|
||||
console.log("results", results);
|
||||
|
||||
results.results.forEach((result, i) => {
|
||||
const r = result as WebSearchResponse;
|
||||
queryResults.push({
|
||||
type: "article",
|
||||
id: `${i}`,
|
||||
title: `${r.title}`,
|
||||
input_message_content: {
|
||||
message_text: `${r.title}\n\n${r.url}`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await bot.answerInlineQuery({
|
||||
inline_query_id: query.id,
|
||||
results: queryResults,
|
||||
});
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
} else {
|
||||
await bot.answerInlineQuery({
|
||||
inline_query_id: query.id,
|
||||
results: [],
|
||||
}).catch(logError);
|
||||
}
|
||||
});
|
||||
|
||||
bot.on("callback_query", async (query) => {
|
||||
console.log(query);
|
||||
await findAndExecuteCallbackCommand(callbackCommands, query);
|
||||
});
|
||||
bot.on("inline_query", processInlineQuery);
|
||||
bot.on("callback_query", processCallbackQuery);
|
||||
|
||||
main().catch(logError);
|
||||
+111
-22
@@ -4,15 +4,18 @@ import {CallbackCommand} from "../base/callback-command";
|
||||
import {
|
||||
CallbackQuery,
|
||||
ChatMember,
|
||||
ChatMemberUpdated,
|
||||
InlineKeyboardMarkup,
|
||||
InlineQuery,
|
||||
InlineQueryResult,
|
||||
Message,
|
||||
ParseMode,
|
||||
PhotoSize,
|
||||
User
|
||||
} from "typescript-telegram-bot-api";
|
||||
import {Environment} from "../common/environment";
|
||||
import {AiProvider, Environment} from "../common/environment";
|
||||
import {TelegramError} from "typescript-telegram-bot-api/dist/errors";
|
||||
import {bot, botUser, commands, messageDao} from "../index";
|
||||
import {bot, botUser, callbackCommands, commands, messageDao, ollama} from "../index";
|
||||
import os from "os";
|
||||
import axios from "axios";
|
||||
import {MessagePart} from "../common/message-part";
|
||||
@@ -30,6 +33,10 @@ import {OllamaChat} from "../commands/ollama-chat";
|
||||
import {getYouTubeVideoId} from "./ytdl";
|
||||
import {YouTubeDownload} from "../commands/youtube-download";
|
||||
import {ChatCommand} from "../base/chat-command";
|
||||
import {WebSearchResponse} from "../model/web-search-response";
|
||||
import {GeminiChat} from "../commands/gemini-chat";
|
||||
import {MistralChat} from "../commands/mistral-chat";
|
||||
import {OpenAIChat} from "../commands/openai-chat";
|
||||
|
||||
export const ignore = () => {
|
||||
};
|
||||
@@ -1028,8 +1035,34 @@ export function boolToEmoji(bool: boolean): string {
|
||||
|
||||
export const albumCache = new Map<string, { messages: Message[], timer: NodeJS.Timeout }>();
|
||||
|
||||
export async function processNewMessage(msg: Message) {
|
||||
console.log("message", msg);
|
||||
async function processAlbum(groupId: string): Promise<string[]> {
|
||||
const entry = albumCache.get(groupId);
|
||||
if (!entry) return;
|
||||
|
||||
const allPhotos = entry.messages
|
||||
.filter(m => m.photo)
|
||||
.map(m => m.photo);
|
||||
|
||||
const allPhotoMaxSizes = await Promise.all(allPhotos.map(photo => getPhotoMaxSize(photo)));
|
||||
const ids = await loadImagesFromFileIds(allPhotoMaxSizes);
|
||||
|
||||
console.log(`Received album ${groupId} with ${ids.length} photos.`);
|
||||
console.log("File IDs:", ids);
|
||||
|
||||
albumCache.delete(groupId);
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function photoPathByUniqueId(uniqueId: string): string {
|
||||
return path.join(Environment.DATA_PATH, "photo", uniqueId + ".jpg");
|
||||
}
|
||||
|
||||
export async function processMyChatMember(u: ChatMemberUpdated): Promise<void> {
|
||||
console.log("my_chat_member", u);
|
||||
}
|
||||
|
||||
export async function processNewMessage(msg: Message): Promise<void> {
|
||||
console.log("New Message", msg);
|
||||
|
||||
let storedMsg: StoredMessage | null = null;
|
||||
|
||||
@@ -1133,30 +1166,86 @@ export async function processNewMessage(msg: Message) {
|
||||
if (!startsWithPrefix && msg.chat.type !== "private") return;
|
||||
if (msg.chat.type === "private" && !Environment.ADMIN_IDS.has(msg.chat.id)) return;
|
||||
|
||||
const chat = commands.find(e => e instanceof OllamaChat);
|
||||
if (await checkRequirements(chat, msg)) {
|
||||
await chat.executeOllama(msg, textToCheck);
|
||||
switch (Environment.DEFAULT_AI_PROVIDER) {
|
||||
case AiProvider.OLLAMA: {
|
||||
await commands.find(e => e instanceof OllamaChat).executeOllama(msg, textToCheck);
|
||||
break;
|
||||
}
|
||||
case AiProvider.GEMINI: {
|
||||
await commands.find(e => e instanceof GeminiChat).executeGemini(msg, textToCheck);
|
||||
break;
|
||||
}
|
||||
case AiProvider.MISTRAL: {
|
||||
await commands.find(e => e instanceof MistralChat).executeMistral(msg, textToCheck);
|
||||
break;
|
||||
}
|
||||
case AiProvider.OPENAI: {
|
||||
await commands.find(e => e instanceof OpenAIChat).executeOpenAI(msg, textToCheck);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function processAlbum(groupId: string): Promise<string[]> {
|
||||
const entry = albumCache.get(groupId);
|
||||
if (!entry) return;
|
||||
export async function processEditedMessage(msg: Message): Promise<void> {
|
||||
console.log("Edited Message", msg);
|
||||
|
||||
const allPhotos = entry.messages
|
||||
.filter(m => m.photo)
|
||||
.map(m => m.photo);
|
||||
await UserStore.put(msg.from);
|
||||
|
||||
const allPhotoMaxSizes = await Promise.all(allPhotos.map(photo => getPhotoMaxSize(photo)));
|
||||
const ids = await loadImagesFromFileIds(allPhotoMaxSizes);
|
||||
if (!extractTextMessage(msg) || msg.from.id === botUser.id) return;
|
||||
|
||||
console.log(`Received album ${groupId} with ${ids.length} photos.`);
|
||||
console.log("File IDs:", ids);
|
||||
|
||||
albumCache.delete(groupId);
|
||||
return ids;
|
||||
await MessageStore.put(msg);
|
||||
}
|
||||
|
||||
export function photoPathByUniqueId(uniqueId: string): string {
|
||||
return path.join(Environment.DATA_PATH, "photo", uniqueId + ".jpg");
|
||||
export async function processInlineQuery(query: InlineQuery): Promise<void> {
|
||||
console.log("InlineQuery", query);
|
||||
|
||||
if (Environment.CREATOR_ID !== query.from.id) {
|
||||
await bot.answerInlineQuery({
|
||||
inline_query_id: query.id,
|
||||
results: [],
|
||||
button: {
|
||||
text: "No access",
|
||||
start_parameter: "nope"
|
||||
}
|
||||
}).catch(logError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.query.trim().length !== 0) {
|
||||
try {
|
||||
const queryResults: InlineQueryResult[] = [];
|
||||
const results = await ollama.webSearch({query: query.query});
|
||||
|
||||
console.log("results", results);
|
||||
|
||||
results.results.forEach((result, i) => {
|
||||
const r = result as WebSearchResponse;
|
||||
queryResults.push({
|
||||
type: "article",
|
||||
id: `${i}`,
|
||||
title: `${r.title}`,
|
||||
input_message_content: {
|
||||
message_text: `${r.title}\n\n${r.url}`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await bot.answerInlineQuery({
|
||||
inline_query_id: query.id,
|
||||
results: queryResults,
|
||||
});
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
} else {
|
||||
await bot.answerInlineQuery({
|
||||
inline_query_id: query.id,
|
||||
results: [],
|
||||
}).catch(logError);
|
||||
}
|
||||
}
|
||||
|
||||
export async function processCallbackQuery(query: CallbackQuery): Promise<void> {
|
||||
console.log("CallbackQuery", query);
|
||||
await findAndExecuteCallbackCommand(callbackCommands, query);
|
||||
}
|
||||
Reference in New Issue
Block a user