From d2464b9b21573524e46a19f3474230b8a3d1b38d Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Sun, 10 May 2026 22:53:43 +0300 Subject: [PATCH] app: wire new commands and update docs --- README.md | 10 ++++- src/index.ts | 103 +++++++++++++++++---------------------------------- 2 files changed, 43 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 14d6dd6..e336a85 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ cp .env.example .env # Edit .env: add BOT_TOKEN, CREATOR_ID and configure optional AI models (GEMINI_API_KEY, MISTRAL_API_KEY, OLLAMA_ADDRESS) ``` +For local Ollama document RAG, install an embedding model locally and set it in `.env`: + +```bash +ollama pull nomic-embed-text +OLLAMA_EMBEDDING_MODEL=nomic-embed-text +``` + **With Bun (Recommended):** ```bash bun install @@ -42,13 +49,14 @@ docker run -d --env-file .env -v $(pwd)/data:/config/data tg-bot-bun ## Requirements -- Node.js >= 18 OR Bun >= 1.0 +- Node.js >= 20 OR Bun >= 1.0 - Docker (optional) ## Features - AI chat (Gemini, Mistral, Ollama) +- Local document RAG for Ollama without third-party providers - Custom answers and commands - Admin management - User blocking (mute/unmute) diff --git a/src/index.ts b/src/index.ts index efd5790..f841c9a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,11 @@ import {Environment} from "./common/environment"; import {BotCommand, TelegramBot, User} from "typescript-telegram-bot-api"; import {Command} from "./base/command"; import { - delay, initSystemSpecs, logError, processCallbackQuery, processEditedMessage, + processGuestMessage, processInlineQuery, processMyChatMember, processNewMessage @@ -27,10 +27,8 @@ import {When} from "./commands/when"; import {RandomInt} from "./commands/random-int"; import {Ban} from "./commands/ban"; import {Quote} from "./commands/quote"; -import {Ollama} from "ollama"; import {OllamaSearch} from "./commands/ollama-search"; import {Id} from "./commands/id"; -import {OllamaPrompt} from "./commands/ollama-prompt"; import {AdminsAdd} from "./commands/admins-add"; import {AdminsRemove} from "./commands/admins-remove"; import {Shutdown} from "./commands/shutdown"; @@ -49,16 +47,14 @@ import {MessageDao} from "./db/message-dao"; import {DatabaseManager} from "./db/database-manager"; import {UserDao} from "./db/user-dao"; import {UserStore} from "./common/user-store"; -import {OllamaRequest} from "./model/ollama-request"; import {CallbackCommand} from "./base/callback-command"; -import {OllamaCancel} from "./callback_commands/ollama-cancel"; +import {AiCancel} from "./callback_commands/ai-cancel"; +import {AiRegenerate} from "./callback_commands/ai-regenerate"; import {MistralChat} from "./commands/mistral-chat"; import {Transliteration} from "./commands/transliteration"; import {OllamaListModels} from "./commands/ollama-list-models"; import {OllamaGetModel} from "./commands/ollama-get-model"; import {OllamaSetModel} from "./commands/ollama-set-model"; -import {Mistral} from "@mistralai/mistralai"; -import {GoogleGenAI} from "@google/genai"; import {MistralGetModel} from "./commands/mistral-get-model"; import {MistralSetModel} from "./commands/mistral-set-model"; import {MistralListModels} from "./commands/mistral-list-models"; @@ -66,20 +62,19 @@ import {GeminiListModels} from "./commands/gemini-list-models"; import {GeminiGetModel} from "./commands/gemini-get-model"; import {GeminiSetModel} from "./commands/gemini-set-model"; import {Debug} from "./commands/debug"; -import {GeminiGenerateImage} from "./commands/gemini-generate-image"; import fs from "node:fs"; import path from "node:path"; -import {setInterval} from "node:timers"; -import {OpenAI} from "openai"; import {OpenAIChat} from "./commands/openai-chat"; import {OpenAIListModels} from "./commands/openai-list-models"; import {OpenAIGetModel} from "./commands/openai-get-model"; import {OpenAISetModel} from "./commands/openai-set-model"; import {Info} from "./commands/info"; -import {OpenAIGenImage} from "./commands/openai-gen-image"; -import {clearUpFolderFromOldFiles} from "./util/files"; import {AdminsList} from "./commands/admins-list"; import {ExportDb} from "./commands/export-db"; +import {Settings} from "./commands/settings"; +import {UserSettingsCallback} from "./callback_commands/user-settings"; +import {TextToSpeech} from "./commands/text-to-speech"; +import {SpeechToText} from "./commands/speech-to-text"; process.setUncaughtExceptionCaptureCallback(logError); @@ -92,42 +87,6 @@ export const userDao = new UserDao(); export const bot = new TelegramBot({botToken: Environment.BOT_TOKEN, testEnvironment: Environment.TEST_ENVIRONMENT}); export let botUser: User; -export const googleAi = new GoogleGenAI({apiKey: Environment.GEMINI_API_KEY}); -export const mistralAi = new Mistral({apiKey: Environment.MISTRAL_API_KEY}); -export const openAi = new OpenAI({apiKey: Environment.OPENAI_API_KEY, baseURL: Environment.OPENAI_BASE_URL, dangerouslyAllowBrowser: true}); - -export const ollama = new Ollama({ - host: Environment.OLLAMA_ADDRESS, - headers: {"Authorization": `Bearer ${Environment.OLLAMA_API_KEY}`} -}); - -export const ollamaRequests: OllamaRequest[] = []; - -export function getOllamaRequest(uuid: string): OllamaRequest | undefined { - return ollamaRequests.find(r => r.uuid === uuid); -} - -export function updateOllamaRequest(uuid: string, request: OllamaRequest) { - const index = ollamaRequests.findIndex(r => r.uuid === uuid); - if (index >= 0) { - ollamaRequests[index] = request; - } -} - -export function abortOllamaRequest(uuid: string): boolean { - const request = getOllamaRequest(uuid); - if (!request || request.done) return false; - - try { - request.stream.abort(); - updateOllamaRequest(uuid, {...request, done: true}); - return true; - } catch (e) { - logError(e); - return false; - } -} - export const commands: Command[] = [ new Start(), new Help(), @@ -157,6 +116,9 @@ export const commands: Command[] = [ new Transliteration(), new Debug(), new Info(), + new Settings(), + new TextToSpeech(), + new SpeechToText(), new AdminsAdd(), new AdminsRemove(), @@ -173,13 +135,14 @@ if (Environment.ENABLE_UNSAFE_EVAL) { } export const callbackCommands: CallbackCommand[] = [ - new OllamaCancel(), + new AiCancel(), + new AiRegenerate(), + new UserSettingsCallback(), ]; -if (Environment.OLLAMA_ADDRESS && Environment.OLLAMA_MODEL) { +if (Environment.OLLAMA_ADDRESS && Environment.OLLAMA_CHAT_MODEL) { commands.push( new OllamaChat(), - new OllamaPrompt(), new OllamaListModels(), new OllamaGetModel(), new OllamaSetModel() @@ -196,7 +159,6 @@ if (Environment.GEMINI_API_KEY) { new GeminiListModels(), new GeminiGetModel(), new GeminiSetModel(), - new GeminiGenerateImage() ); } @@ -215,14 +177,16 @@ if (Environment.OPENAI_API_KEY) { new OpenAIListModels(), new OpenAIGetModel(), new OpenAISetModel(), - new OpenAIGenImage() ); } export const cacheDir = path.join(Environment.DATA_PATH, "cache"); export const photoDir = path.join(cacheDir, "photo"); export const photoGenDir = path.join(photoDir, "gen"); +export const documentDir = path.join(cacheDir, "document"); +export const audioDir = path.join(cacheDir, "audio"); export const videoDir = path.join(cacheDir, "video"); +export const videoNotesDir = path.join(cacheDir, "video-note"); export const videoTempDir = path.join(videoDir, "temp"); let isShuttingDown = false; @@ -253,29 +217,29 @@ async function main() { `DEFAULT_AI_PROVIDER: ${Environment.DEFAULT_AI_PROVIDER}` ); - const dirsToCheck = [cacheDir, photoDir, photoGenDir, videoDir, videoTempDir]; + const dirsToCheck = [cacheDir, photoDir, photoGenDir, documentDir, audioDir, videoDir, videoNotesDir, videoTempDir]; dirsToCheck.forEach(dir => { if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); + fs.mkdirSync(dir, {recursive: true}); } }); - const now = new Date(); + // const now = new Date(); - const midnight = new Date(); - midnight.setHours(0, 0, 0, 0); - midnight.setDate(now.getDate() + 1); + // const midnight = new Date(); + // midnight.setHours(0, 0, 0, 0); + // midnight.setDate(now.getDate() + 1); - const diff = midnight.getTime() - now.getTime(); - console.log("Clearing up cache will be started in " + diff + "ms"); + // const diff = midnight.getTime() - now.getTime(); + // console.log("Clearing up cache will be started in " + diff + "ms"); - clearUpFolderFromOldFiles(cacheDir); - delay(diff).then(() => { - setInterval(() => { - console.log("Started clearing up cache"); - clearUpFolderFromOldFiles(cacheDir); - }, 1000 * 60 * 60 * 24); - }); + // clearUpFolderFromOldFiles(cacheDir); + // delay(diff).then(() => { + // setInterval(() => { + // console.log("Started clearing up cache"); + // clearUpFolderFromOldFiles(cacheDir); + // }, 1000 * 60 * 60 * 24); + // }); const cmds = commands.filter(cmd => { return cmd.title && cmd.title.startsWith("/") && cmd.title.split(" ").length === 1 && cmd.description; @@ -311,6 +275,7 @@ bot.on("edited_message", processEditedMessage); bot.on("message", processNewMessage); bot.on("inline_query", processInlineQuery); bot.on("callback_query", processCallbackQuery); +bot.on("guest_message", processGuestMessage); process.on("SIGTERM", () => { shutdown("SIGTERM").catch(logError); @@ -320,4 +285,4 @@ process.on("SIGINT", () => { shutdown("SIGINT").catch(logError); }); -main().catch(logError); \ No newline at end of file +main().catch(logError);