support for thinking models, ux improvements

This commit is contained in:
2026-01-21 20:10:10 +03:00
parent d029d53fee
commit 193bb3d3ad
5 changed files with 115 additions and 36 deletions
+30 -7
View File
@@ -1,8 +1,12 @@
import {CallbackCommand} from "../base/callback-command"; import {CallbackCommand} from "../base/callback-command";
import {CallbackQuery} from "typescript-telegram-bot-api"; import {CallbackQuery} from "typescript-telegram-bot-api";
import {abortOllamaRequest, bot, getOllamaRequest} from "../index"; import {abortOllamaRequest, bot, getOllamaRequest} from "../index";
import {logError} from "../util/utils";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
import {logError} from "../util/utils";
import {MessageStore} from "../common/message-store";
import {StoredMessage} from "../model/stored-message";
const cancelledText = "```Ollama\n❌ Отменено```";
export class OllamaCancel extends CallbackCommand { export class OllamaCancel extends CallbackCommand {
@@ -18,16 +22,35 @@ export class OllamaCancel extends CallbackCommand {
if (!uuid) return; if (!uuid) return;
const request = getOllamaRequest(uuid); const request = getOllamaRequest(uuid);
if (!request) return; if (request) {
if (request.fromId !== fromId && fromId !== Environment.CREATOR_ID) return; if (request.fromId !== fromId && fromId !== Environment.CREATOR_ID) return;
const aborted = abortOllamaRequest(uuid); const aborted = abortOllamaRequest(uuid);
console.log(`aborted request ${uuid}:`, aborted); console.log(`aborted request ${uuid}:`, aborted);
}
await bot.editMessageReplyMarkup({ let msg: StoredMessage | null = null;
try {
msg = await MessageStore.get(chatId, messageId);
} catch (e) {
logError(e);
}
let content: string | null = null;
if (msg?.text?.trim()?.length > 0) {
content = msg?.text.trim();
if (content.length + cancelledText.length > 4096) {
content = content.substring(0, 4096 - cancelledText.length - 2) + "\n";
}
}
await bot.editMessageText({
chat_id: chatId, chat_id: chatId,
message_id: messageId, message_id: messageId,
reply_markup: {inline_keyboard: []} text: `${content}${cancelledText}`,
parse_mode: "Markdown",
reply_markup: {inline_keyboard: []},
}).catch(logError); }).catch(logError);
} }
} }
+55 -13
View File
@@ -57,7 +57,6 @@ export class OllamaChat extends ChatCommand {
waitMessage = await bot.sendMessage({ waitMessage = await bot.sendMessage({
chat_id: chatId, chat_id: chatId,
text: Environment.waitText, text: Environment.waitText,
// text: maxSize !== null ? `🔍 Внимательно изучаю изображение...\n🤓 ${maxSize.width}x${maxSize.height}px` : Environment.waitText,
reply_parameters: { reply_parameters: {
chat_id: chatId, chat_id: chatId,
message_id: msg.message_id message_id: msg.message_id
@@ -68,9 +67,9 @@ export class OllamaChat extends ChatCommand {
const stream = await ollama.chat({ const stream = await ollama.chat({
model: Environment.OLLAMA_MODEL, model: Environment.OLLAMA_MODEL,
stream: true, stream: true,
think: false,
keep_alive: 300, keep_alive: 300,
messages: chatMessages think: true,
messages: chatMessages,
}); });
ollamaRequests.push({uuid: uuid, stream: stream, done: false, fromId: msg.from.id, chatId: msg.chat.id}); ollamaRequests.push({uuid: uuid, stream: stream, done: false, fromId: msg.from.id, chatId: msg.chat.id});
@@ -79,16 +78,25 @@ export class OllamaChat extends ChatCommand {
let shouldBreak = false; let shouldBreak = false;
const editor = startIntervalEditor({ const editor = startIntervalEditor({
uuid: uuid,
intervalMs: 4500, intervalMs: 4500,
getText: () => currentText, getText: () => currentText,
editFn: async (text) => { editFn: async (text) => {
await editMessageText( try {
chatId, await editMessageText(
waitMessage.message_id, chatId,
escapeMarkdownV2Text(text), waitMessage.message_id,
"Markdown", escapeMarkdownV2Text(text),
isOver ? {inline_keyboard: []} : cancelMarkup "Markdown",
).catch(logError); isOver ? {inline_keyboard: []} : cancelMarkup
);
waitMessage.reply_to_message = msg;
waitMessage.text = text;
await MessageStore.put(waitMessage);
} catch (e) {
logError(e);
}
}, },
onStop: async () => { onStop: async () => {
} }
@@ -96,8 +104,43 @@ export class OllamaChat extends ChatCommand {
await editor.tick(); await editor.tick();
try { try {
let isThinking = false;
for await (const chunk of stream) { for await (const chunk of stream) {
currentText += chunk.message.content; const content = chunk.message.content;
if (content === "<think>" || chunk.message.thinking) {
if (!isThinking) {
await editMessageText(
chatId,
waitMessage.message_id,
"🤔 Размышляю...",
"Markdown",
isOver ? {inline_keyboard: []} : cancelMarkup
).catch(logError);
console.log("Thinking:\n");
}
isThinking = true;
}
if (chunk.message.thinking) {
console.log(chunk.message.thinking);
} else {
console.log(chunk.message.content);
}
if (!isThinking) {
currentText += content;
}
if (isThinking && !chunk.message.thinking) {
currentText += content;
}
if (content === "</think>" || !chunk.message.thinking) {
isThinking = false;
}
if (currentText.length > 4096) { if (currentText.length > 4096) {
currentText = currentText.slice(0, 4093) + "..."; currentText = currentText.slice(0, 4093) + "...";
@@ -130,8 +173,7 @@ export class OllamaChat extends ChatCommand {
waitMessage.reply_to_message = msg; waitMessage.reply_to_message = msg;
waitMessage.text = currentText; waitMessage.text = currentText;
await MessageStore.put(waitMessage); await MessageStore.put(waitMessage);
await oldReplyToMessage(waitMessage, `⏱️ ${diff}s`);
await oldReplyToMessage(waitMessage, `⏱️ ${diff}s` /*+ (maxSize !== null ? `\n🤓 ${maxSize.width}x${maxSize.height}px` : "")*/);
break; break;
} }
} }
+14 -10
View File
@@ -9,18 +9,22 @@ export class OllamaGetModel extends ChatCommand {
description = "Ollama model info"; description = "Ollama model info";
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
try {
const showResponse = await ollama.show({model: Environment.OLLAMA_MODEL});
const showResponse = await ollama.show({model: Environment.OLLAMA_MODEL}); const caps = showResponse.capabilities;
const caps = showResponse.capabilities; const text = "```Ollama\n" +
`model: ${Environment.OLLAMA_MODEL}\n\n` +
`vision: ${boolToEmoji(caps.includes("vision"))}\n` +
`thinking: ${boolToEmoji(caps.includes("thinking"))}\n` +
`tools: ${boolToEmoji(caps.includes("tools"))}`
+ "```";
const text = "```ollama\n" + await replyToMessage({message: msg, text: text, parse_mode: "Markdown"}).catch(logError);
`model: ${Environment.OLLAMA_MODEL}\n\n` + } catch (e) {
`vision: ${boolToEmoji(caps.includes("vision"))}\n` + logError(e);
`thinking: ${boolToEmoji(caps.includes("thinking"))}\n` + await replyToMessage({message: msg, text: e.toString()}).catch(logError);
`tools: ${boolToEmoji(caps.includes("tools"))}` }
+ "```";
await replyToMessage({message: msg, text: text, parse_mode: "Markdown"}).catch(logError);
} }
} }
+13 -4
View File
@@ -4,6 +4,7 @@ import {Environment} from "../common/environment";
import {logError, replyToMessage} from "../util/utils"; 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";
export class OllamaSetModel extends ChatCommand { export class OllamaSetModel extends ChatCommand {
argsMode = "required" as const; argsMode = "required" as const;
@@ -15,11 +16,19 @@ export class OllamaSetModel extends ChatCommand {
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];
Environment.setOllamaModel(newModel || Environment.OLLAMA_MODEL);
const text = newModel ? `Выбрана модель "${newModel}"` try {
: `Модель не задана. Будет использоваться стандартная модель "${Environment.OLLAMA_MODEL}".`; await ollama.show({model: newModel});
await replyToMessage({message: msg, text: text}).catch(logError); Environment.setOllamaModel(newModel || Environment.OLLAMA_MODEL);
const text = newModel ? `Выбрана модель "${newModel}"`
: `Модель не задана. Будет использоваться стандартная модель "${Environment.OLLAMA_MODEL}".`;
await replyToMessage({message: msg, text: text}).catch(logError);
} catch (e) {
logError(e);
await replyToMessage({message: msg, text: e.toString()}).catch(logError);
}
} }
} }
+3 -2
View File
@@ -4,7 +4,7 @@ import {CallbackCommand} from "../base/callback-command";
import {CallbackQuery, InlineKeyboardMarkup, Message, ParseMode, PhotoSize, User} from "typescript-telegram-bot-api"; import {CallbackQuery, InlineKeyboardMarkup, Message, ParseMode, PhotoSize, User} from "typescript-telegram-bot-api";
import {Environment} from "../common/environment"; import {Environment} from "../common/environment";
import {TelegramError} from "typescript-telegram-bot-api/dist/errors"; import {TelegramError} from "typescript-telegram-bot-api/dist/errors";
import {bot, botUser, messageDao, setSystemInfo} from "../index"; import {bot, botUser, getOllamaRequest, messageDao, setSystemInfo} from "../index";
import os from "os"; import os from "os";
import axios from "axios"; import axios from "axios";
import {MessagePart} from "../common/message-part"; import {MessagePart} from "../common/message-part";
@@ -746,6 +746,7 @@ function toHex(v: number) {
} }
export function startIntervalEditor(params: { export function startIntervalEditor(params: {
uuid?: string;
intervalMs: number; intervalMs: number;
getText: () => string; getText: () => string;
editFn: (text: string) => Promise<void>; editFn: (text: string) => Promise<void>;
@@ -755,7 +756,7 @@ export function startIntervalEditor(params: {
let stopped = false; let stopped = false;
const tick = async () => { const tick = async () => {
if (stopped) return; if (stopped || (params.uuid && getOllamaRequest(params.uuid)?.done)) return;
const next = params.getText(); const next = params.getText();
if (!next || next === lastSent) return; if (!next || next === lastSent) return;