feat(ai): improve runtime capability reporting and context settings

Add explicit chat capability tracking, expose formatted runtime capabilities
in the info command, and support a max context size option for user AI settings.

Also update Ollama base URL resolution to use OLLAMA_ADDRESS and simplify
provider chat command execution.
This commit is contained in:
2026-05-11 02:01:44 +03:00
parent d2464b9b21
commit 3848dd82d9
15 changed files with 850 additions and 336 deletions
+2 -2
View File
@@ -41,13 +41,13 @@
"idFromLabelText": "id пользователя", "idFromLabelText": "id пользователя",
"idReplyLabelText": "id ответа", "idReplyLabelText": "id ответа",
"runtimeProviderLabelText": "провайдер", "runtimeProviderLabelText": "провайдер",
"runtimeProviderCurrentLabelText": "текущий",
"runtimeModelLabelText": "модель", "runtimeModelLabelText": "модель",
"runtimeCapabilitiesLabelText": "возможности", "runtimeCapabilitiesLabelText": "возможности",
"runtimeExternalLabelText": "внешний", "runtimeExternalLabelText": "внешний",
"infoAiBlockLabelText": "AI", "infoAiBlockLabelText": "AI",
"infoSupportedProvidersLabelText": "поддерживаемые провайдеры", "infoSupportedProvidersLabelText": "провайдеры",
"infoToolsBlockLabelText": "инструменты", "infoToolsBlockLabelText": "инструменты",
"infoCountLabelText": "количество",
"infoCommandsBlockLabelText": "команды", "infoCommandsBlockLabelText": "команды",
"infoPublicLabelText": "публичные", "infoPublicLabelText": "публичные",
"infoPrivateLabelText": "приватные", "infoPrivateLabelText": "приватные",
+1 -2
View File
@@ -45,9 +45,8 @@
"runtimeCapabilitiesLabelText": "можливості", "runtimeCapabilitiesLabelText": "можливості",
"runtimeExternalLabelText": "зовнішній", "runtimeExternalLabelText": "зовнішній",
"infoAiBlockLabelText": "AI", "infoAiBlockLabelText": "AI",
"infoSupportedProvidersLabelText": "підтримувані провайдери", "infoSupportedProvidersLabelText": "провайдери",
"infoToolsBlockLabelText": "інструменти", "infoToolsBlockLabelText": "інструменти",
"infoCountLabelText": "кількість",
"infoCommandsBlockLabelText": "команди", "infoCommandsBlockLabelText": "команди",
"infoPublicLabelText": "публічні", "infoPublicLabelText": "публічні",
"infoPrivateLabelText": "приватні", "infoPrivateLabelText": "приватні",
+1 -1
View File
@@ -74,7 +74,7 @@ function modelEnvNames(provider: AiProvider, purpose: AiRuntimePurpose): string[
export function getProviderBaseUrl(provider: AiProvider): string | undefined { export function getProviderBaseUrl(provider: AiProvider): string | undefined {
switch (provider) { switch (provider) {
case AiProvider.OLLAMA: case AiProvider.OLLAMA:
return env("OLLAMA_ENDPOINT"); return env("OLLAMA_ADDRESS");
case AiProvider.GEMINI: case AiProvider.GEMINI:
return env("GEMINI_BASE_URL") ?? env("GEMINI_ENDPOINT") return env("GEMINI_BASE_URL") ?? env("GEMINI_ENDPOINT")
?? (Environment.GEMINI_API_MODE === "openai" ? GEMINI_OPENAI_BASE_URL : undefined); ?? (Environment.GEMINI_API_MODE === "openai" ? GEMINI_OPENAI_BASE_URL : undefined);
+28 -18
View File
@@ -17,13 +17,8 @@ import {
sameRuntimeEndpoint, sameRuntimeEndpoint,
} from "./ai-runtime-target"; } from "./ai-runtime-target";
export type RuntimeModelInfo = {
provider: AiProvider;
model: string;
capabilities: AiModelCapabilities;
};
const CAPABILITY_NAMES: AiCapabilityName[] = [ const CAPABILITY_NAMES: AiCapabilityName[] = [
"chat",
"vision", "vision",
"ocr", "ocr",
"thinking", "thinking",
@@ -84,6 +79,7 @@ function capability(supported: boolean, target?: AiRuntimeTarget, runtimeTarget?
function buildCapabilities(overrides: Partial<Record<AiCapabilityName, AiCapabilityInfo>>): AiModelCapabilities { function buildCapabilities(overrides: Partial<Record<AiCapabilityName, AiCapabilityInfo>>): AiModelCapabilities {
return Object.assign(new AiModelCapabilities(), { return Object.assign(new AiModelCapabilities(), {
chat: {supported: false},
vision: {supported: false}, vision: {supported: false},
ocr: {supported: false}, ocr: {supported: false},
thinking: {supported: false}, thinking: {supported: false},
@@ -154,6 +150,7 @@ export async function getModelCapabilities(
const documentsTarget = resolveAiRuntimeTarget(provider, "documents"); const documentsTarget = resolveAiRuntimeTarget(provider, "documents");
return buildCapabilities({ return buildCapabilities({
chat: capability(true, target, runtimeTarget),
vision: capability(has("vision"), target, runtimeTarget), vision: capability(has("vision"), target, runtimeTarget),
ocr: capability(has("ocr"), target, runtimeTarget), ocr: capability(has("ocr"), target, runtimeTarget),
thinking: capability(has("thinking"), target, runtimeTarget), thinking: capability(has("thinking"), target, runtimeTarget),
@@ -172,6 +169,7 @@ export async function getModelCapabilities(
const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech"); const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech");
return buildCapabilities({ return buildCapabilities({
chat: capability(true, target, runtimeTarget),
vision: capability(chatLike, target, runtimeTarget), vision: capability(chatLike, target, runtimeTarget),
ocr: capability(chatLike, target, runtimeTarget), ocr: capability(chatLike, target, runtimeTarget),
thinking: capability(reasoningModel, target, runtimeTarget), thinking: capability(reasoningModel, target, runtimeTarget),
@@ -191,6 +189,7 @@ export async function getModelCapabilities(
const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech"); const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech");
return buildCapabilities({ return buildCapabilities({
chat: capability(true, target, runtimeTarget),
vision: capability(!!caps?.vision, target, runtimeTarget), vision: capability(!!caps?.vision, target, runtimeTarget),
ocr: capability(!!caps?.ocr, target, runtimeTarget), ocr: capability(!!caps?.ocr, target, runtimeTarget),
thinking: capability(!!caps?.reasoning, target, runtimeTarget), thinking: capability(!!caps?.reasoning, target, runtimeTarget),
@@ -209,6 +208,7 @@ export async function getModelCapabilities(
const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech"); const ttsTarget = resolveAiRuntimeTarget(provider, "textToSpeech");
return buildCapabilities({ return buildCapabilities({
chat: capability(true, target, runtimeTarget),
vision: capability(isOpenAiVisionModel(model), target, runtimeTarget), vision: capability(isOpenAiVisionModel(model), target, runtimeTarget),
ocr: capability(isOpenAiVisionModel(model), target, runtimeTarget), ocr: capability(isOpenAiVisionModel(model), target, runtimeTarget),
thinking: capability(reasoningModel, target, runtimeTarget), thinking: capability(reasoningModel, target, runtimeTarget),
@@ -227,13 +227,13 @@ export async function getModelCapabilities(
} }
} }
export async function getRuntimeCapabilities( export async function getRuntimeCapabilities(
provider: AiProvider = Environment.DEFAULT_AI_PROVIDER, provider: AiProvider = Environment.DEFAULT_AI_PROVIDER,
model: string | undefined = getRuntimeModel(provider) model: string | undefined = getRuntimeModel(provider),
target?: AiRuntimeTarget
): Promise<AiModelCapabilities> { ): Promise<AiModelCapabilities> {
const runtimeTarget = resolveAiRuntimeTarget(provider, "chat", model ?? getRuntimeModel(provider)); const runtimeTarget = target ?? resolveAiRuntimeTarget(provider, "chat", model ?? getRuntimeModel(provider));
const result = await getModelCapabilities(provider, runtimeTarget.model, "chat") ?? buildCapabilities({}); const result = await getModelCapabilities(provider, runtimeTarget.model, target?.purpose ?? "chat") ?? buildCapabilities({});
for (const capabilityName of CAPABILITY_NAMES) { for (const capabilityName of CAPABILITY_NAMES) {
const target = resolveAiRuntimeTarget(provider, capabilityName); const target = resolveAiRuntimeTarget(provider, capabilityName);
@@ -249,12 +249,13 @@ export async function getRuntimeCapabilities(
return result; return result;
} }
export async function formatRuntimeModelInfo( export async function getFormattedCapabilities(
provider: AiProvider = Environment.DEFAULT_AI_PROVIDER, provider: AiProvider = Environment.DEFAULT_AI_PROVIDER,
model: string | undefined = getRuntimeModel(provider), model: string | undefined = getRuntimeModel(provider),
caps?: AiModelCapabilities caps?: AiModelCapabilities,
): Promise<string> { ): Promise<string[]> {
if (!caps) caps = await getRuntimeCapabilities(provider, model); if (!caps) caps = await getRuntimeCapabilities(provider, model);
const line = (title: string, value?: AiCapabilityInfo) => { const line = (title: string, value?: AiCapabilityInfo) => {
const state = value?.supported ? "✅" : "❌"; const state = value?.supported ? "✅" : "❌";
const external = value?.external ?? (!!value?.model && value.model !== model); const external = value?.external ?? (!!value?.model && value.model !== model);
@@ -267,10 +268,8 @@ export async function formatRuntimeModelInfo(
}); });
}; };
return Environment.getRuntimeModelInfoText( return [
provider.toString().toLowerCase(), line(Environment.runtimeCapabilityChatText, caps.chat),
model,
[
line(Environment.runtimeCapabilityVisionText, caps.vision), line(Environment.runtimeCapabilityVisionText, caps.vision),
line(Environment.runtimeCapabilityOcrText, caps.ocr), line(Environment.runtimeCapabilityOcrText, caps.ocr),
line(Environment.runtimeCapabilityThinkingText, caps.thinking), line(Environment.runtimeCapabilityThinkingText, caps.thinking),
@@ -281,7 +280,18 @@ export async function formatRuntimeModelInfo(
line(Environment.runtimeCapabilityTextToSpeechText, caps.textToSpeech), line(Environment.runtimeCapabilityTextToSpeechText, caps.textToSpeech),
line(Environment.runtimeCapabilityDocumentsText, caps.documents), line(Environment.runtimeCapabilityDocumentsText, caps.documents),
line(Environment.runtimeCapabilityOutputImagesText, caps.outputImages), line(Environment.runtimeCapabilityOutputImagesText, caps.outputImages),
], ];
}
export async function formatRuntimeModelInfo(
provider: AiProvider = Environment.DEFAULT_AI_PROVIDER,
model: string | undefined = getRuntimeModel(provider),
caps?: AiModelCapabilities,
): Promise<string> {
return Environment.getRuntimeModelInfoText(
provider.toString().toLowerCase(),
model,
await getFormattedCapabilities(provider, model, caps)
); );
} }
+38 -44
View File
@@ -75,8 +75,9 @@ const TELEGRAM_LIMIT = 4096;
const MAX_TOOL_ROUNDS = 20; const MAX_TOOL_ROUNDS = 20;
const OPENAI_IMAGE_PARTIALS = 3; const OPENAI_IMAGE_PARTIALS = 3;
const AI_REQUEST_TIMEOUT_MS = 10 * 60 * 1000; const AI_REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
const DEFAULT_OLLAMA_CONTEXT_SIZE = 256000;
const MIN_OLLAMA_CONTEXT_SIZE = 4096; const MIN_OLLAMA_CONTEXT_SIZE = 4096;
const MAX_OLLAMA_CONTEXT_SIZE = 262144;
const DEFAULT_OLLAMA_CONTEXT_SIZE = 32768;
const toolResourceLocks = new KeyedAsyncLock(); const toolResourceLocks = new KeyedAsyncLock();
type UnifiedRunOptions = { type UnifiedRunOptions = {
@@ -134,11 +135,7 @@ type RuntimeConfigSnapshot = {
useNamesInPrompt: boolean; useNamesInPrompt: boolean;
useSystemPrompt: boolean; useSystemPrompt: boolean;
systemPrompt?: string; systemPrompt?: string;
ollamaModel: string;
ollamaImageModel: string;
ollamaThinkModel: string;
ollamaAudioModel: string;
ollamaEmbeddingModel: string;
ollamaChatTarget: AiRuntimeTarget; ollamaChatTarget: AiRuntimeTarget;
ollamaVisionTarget: AiRuntimeTarget; ollamaVisionTarget: AiRuntimeTarget;
ollamaThinkingTarget: AiRuntimeTarget; ollamaThinkingTarget: AiRuntimeTarget;
@@ -152,30 +149,23 @@ type RuntimeConfigSnapshot = {
ollamaRagMaxArchiveFiles: number; ollamaRagMaxArchiveFiles: number;
ollamaRagMaxArchiveBytes: number; ollamaRagMaxArchiveBytes: number;
ollamaRagMaxArchiveDepth: number; ollamaRagMaxArchiveDepth: number;
geminiModel: string;
geminiImageModel: string;
geminiTranscriptionModel: string;
geminiChatTarget: AiRuntimeTarget; geminiChatTarget: AiRuntimeTarget;
mistralModel: string;
mistralTranscriptionModel: string;
mistralChatTarget: AiRuntimeTarget; mistralChatTarget: AiRuntimeTarget;
openAiModel: string;
openAiImageModel: string;
openAiTranscriptionModel: string;
openAiChatTarget: AiRuntimeTarget; openAiChatTarget: AiRuntimeTarget;
openAiImageTarget: AiRuntimeTarget; openAiImageTarget: AiRuntimeTarget;
}; };
function snapshotRuntimeConfig(): RuntimeConfigSnapshot { // TODO: 11/05/2026, Danil Nikolaev: remove export
export function snapshotRuntimeConfig(): RuntimeConfigSnapshot {
return { return {
useNamesInPrompt: Environment.USE_NAMES_IN_PROMPT, useNamesInPrompt: Environment.USE_NAMES_IN_PROMPT,
useSystemPrompt: Environment.USE_SYSTEM_PROMPT, useSystemPrompt: Environment.USE_SYSTEM_PROMPT,
systemPrompt: Environment.SYSTEM_PROMPT, systemPrompt: Environment.SYSTEM_PROMPT,
ollamaModel: Environment.OLLAMA_CHAT_MODEL,
ollamaImageModel: Environment.OLLAMA_IMAGE_MODEL,
ollamaThinkModel: Environment.OLLAMA_THINK_MODEL,
ollamaAudioModel: Environment.OLLAMA_AUDIO_MODEL,
ollamaEmbeddingModel: Environment.OLLAMA_EMBEDDING_MODEL,
ollamaChatTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "chat"), ollamaChatTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "chat"),
ollamaVisionTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "vision"), ollamaVisionTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "vision"),
ollamaThinkingTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "thinking"), ollamaThinkingTarget: resolveAiRuntimeTarget(AiProvider.OLLAMA, "thinking"),
@@ -189,16 +179,11 @@ function snapshotRuntimeConfig(): RuntimeConfigSnapshot {
ollamaRagMaxArchiveFiles: Environment.OLLAMA_RAG_MAX_ARCHIVE_FILES, ollamaRagMaxArchiveFiles: Environment.OLLAMA_RAG_MAX_ARCHIVE_FILES,
ollamaRagMaxArchiveBytes: Environment.OLLAMA_RAG_MAX_ARCHIVE_BYTES, ollamaRagMaxArchiveBytes: Environment.OLLAMA_RAG_MAX_ARCHIVE_BYTES,
ollamaRagMaxArchiveDepth: Environment.OLLAMA_RAG_MAX_ARCHIVE_DEPTH, ollamaRagMaxArchiveDepth: Environment.OLLAMA_RAG_MAX_ARCHIVE_DEPTH,
geminiModel: Environment.GEMINI_MODEL,
geminiImageModel: Environment.GEMINI_IMAGE_MODEL,
geminiTranscriptionModel: Environment.GEMINI_TRANSCRIPTION_MODEL,
geminiChatTarget: resolveAiRuntimeTarget(AiProvider.GEMINI, "chat"), geminiChatTarget: resolveAiRuntimeTarget(AiProvider.GEMINI, "chat"),
mistralModel: Environment.MISTRAL_MODEL,
mistralTranscriptionModel: Environment.MISTRAL_TRANSCRIPTION_MODEL,
mistralChatTarget: resolveAiRuntimeTarget(AiProvider.MISTRAL, "chat"), mistralChatTarget: resolveAiRuntimeTarget(AiProvider.MISTRAL, "chat"),
openAiModel: Environment.OPENAI_MODEL,
openAiImageModel: Environment.OPENAI_IMAGE_MODEL,
openAiTranscriptionModel: Environment.OPENAI_TRANSCRIPTION_MODEL,
openAiChatTarget: resolveAiRuntimeTarget(AiProvider.OPENAI, "chat"), openAiChatTarget: resolveAiRuntimeTarget(AiProvider.OPENAI, "chat"),
openAiImageTarget: resolveAiRuntimeTarget(AiProvider.OPENAI, "outputImages"), openAiImageTarget: resolveAiRuntimeTarget(AiProvider.OPENAI, "outputImages"),
}; };
@@ -234,12 +219,12 @@ function geminiAudioMimeType(mimeType: string | undefined): string {
} }
} }
function snapshotModel(provider: AiProvider, config: RuntimeConfigSnapshot): string { export function snapshotModel(provider: AiProvider, config: RuntimeConfigSnapshot): string {
switch (provider) { switch (provider) {
case AiProvider.OLLAMA: case AiProvider.OLLAMA:
return config.ollamaChatTarget.model; return config.ollamaChatTarget.model;
case AiProvider.GEMINI: case AiProvider.GEMINI:
return config.geminiModel; return config.geminiChatTarget.model;
case AiProvider.MISTRAL: case AiProvider.MISTRAL:
return config.mistralChatTarget.model; return config.mistralChatTarget.model;
case AiProvider.OPENAI: case AiProvider.OPENAI:
@@ -247,6 +232,25 @@ function snapshotModel(provider: AiProvider, config: RuntimeConfigSnapshot): str
} }
} }
export function providerTargets(provider: AiProvider, config: RuntimeConfigSnapshot): AiRuntimeTarget[] {
switch (provider) {
case AiProvider.OLLAMA:
return [
config.ollamaChatTarget,
config.ollamaVisionTarget,
config.ollamaThinkingTarget,
config.ollamaAudioTarget,
config.ollamaDocumentsTarget
];
case AiProvider.GEMINI:
return [config.geminiChatTarget];
case AiProvider.MISTRAL:
return [config.mistralChatTarget];
case AiProvider.OPENAI:
return [config.openAiChatTarget];
}
}
function providerName(provider: AiProvider): AiProviderName { function providerName(provider: AiProvider): AiProviderName {
switch (provider) { switch (provider) {
case AiProvider.OLLAMA: case AiProvider.OLLAMA:
@@ -1422,12 +1426,10 @@ async function runOllama(
const modelInfo = await ollama.show({model: model}); const modelInfo = await ollama.show({model: model});
const contextKey = Object.keys(modelInfo.model_info).find(k => k.endsWith(".context_length")); const contextKey = Object.keys(modelInfo.model_info).find(k => k.endsWith(".context_length"));
// @ts-ignore // @ts-ignore
const maxContextLength = contextKey ? <number>modelInfo?.model_info?.[contextKey] : 32768; const maxContextLength = contextKey ? <number>modelInfo?.model_info?.[contextKey] : DEFAULT_OLLAMA_CONTEXT_SIZE;
const context = clamp(contextSize ?? DEFAULT_OLLAMA_CONTEXT_SIZE, MIN_OLLAMA_CONTEXT_SIZE, maxContextLength ?? 32768); const context = clamp(contextSize ?? contextSize === -1 ? MAX_OLLAMA_CONTEXT_SIZE : DEFAULT_OLLAMA_CONTEXT_SIZE, MIN_OLLAMA_CONTEXT_SIZE, maxContextLength ?? DEFAULT_OLLAMA_CONTEXT_SIZE);
console.log("MESSAGES", messages); await unloadAllOllamaModels(ollama, [model, config.ollamaDocumentsTarget.model]);
await unloadAllOllamaModels(ollama, [model, config.ollamaEmbeddingModel]);
if (!(await isOllamaModelActive(target))) { if (!(await isOllamaModelActive(target))) {
const currentStatus = streamMessage.getStatus(); const currentStatus = streamMessage.getStatus();
@@ -1466,15 +1468,7 @@ async function runOllama(
try { try {
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) { for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
// if (round === 0) { console.log("CONTENT_LENGTH", context);
// streamMessage.setStatus(Environment.weightsLoadingText);
// await streamMessage.flush();
// } else {
// streamMessage.setStatus(roundStatus(round, firstRoundStatus));
// await streamMessage.flush();
// }
console.log("MAX_CONTENT_LENGTH", context);
const request: ChatRequest = { const request: ChatRequest = {
model: model, model: model,
+1 -1
View File
@@ -71,7 +71,7 @@ export class UserSettingsCallback extends CallbackCommand {
if (parsed.screen === "contextSize" && parsed.contextSizeChoice) { if (parsed.screen === "contextSize" && parsed.contextSizeChoice) {
const choice = normalizeAiContextSizeChoice(parsed.contextSizeChoice); const choice = normalizeAiContextSizeChoice(parsed.contextSizeChoice);
if (choice) { if (choice || choice === -1) {
const result = await setUserAiContextSizeChoice(query.from.id, choice); const result = await setUserAiContextSizeChoice(query.from.id, choice);
settings = result.settings; settings = result.settings;
} }
+1 -5
View File
@@ -16,10 +16,6 @@ export class GeminiChat extends ChatCommand {
description = Environment.commandDescriptions.geminiChat; description = Environment.commandDescriptions.geminiChat;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
return this.executeGemini(msg, match?.[3] || ""); await runUnifiedAi({provider: AiProvider.GEMINI, msg: msg, text: match?.[3] ?? "", stream: true});
}
async executeGemini(msg: Message, text: string, stream: boolean = true): Promise<void> {
await runUnifiedAi({provider: AiProvider.GEMINI, msg, text, stream});
} }
} }
+20 -26
View File
@@ -2,13 +2,13 @@ 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 {getCurrentModel, logError, replyToMessage} from "../util/utils"; import {logError, replyToMessage} from "../util/utils";
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 {getProviderTools} from "../ai/tool-mappers";
import {prepareTelegramMarkdownV2} from "../util/markdown-v2-renderer"; import {prepareTelegramMarkdownV2} from "../util/markdown-v2-renderer";
import {resolveEffectiveAiProviderForUser} from "../common/user-ai-settings";
import {getFormattedCapabilities} from "../ai/provider-model-runtime";
export class Info extends Command { export class Info extends Command {
command = ["info", "v"]; command = ["info", "v"];
@@ -17,30 +17,10 @@ export class Info extends Command {
description = Environment.commandDescriptions.info; description = Environment.commandDescriptions.info;
async execute(msg: Message): Promise<void> { async execute(msg: Message): Promise<void> {
const aiProvider = Environment.DEFAULT_AI_PROVIDER; if (!msg.from) return;
const aiModel = getCurrentModel();
if (!aiModel) return;
let aiModelCapabilities: AiModelCapabilities | null = null;
try {
aiModelCapabilities = await getRuntimeCapabilities(aiProvider, aiModel);
} catch (e) {
logError(e);
await replyToMessage({message: msg, text: Environment.getErrorText(e)}).catch(logError);
return;
}
const supportedProvidersLength = Object.keys(AiProvider).filter(key => isNaN(Number(key))).length;
const getAiInfo = async () => {
return Environment.getInfoAiBlockText(
supportedProvidersLength,
await formatRuntimeModelInfo(aiProvider, aiModel, aiModelCapabilities),
);
};
const getToolsInfo = async () => { const getToolsInfo = async () => {
const tools = getProviderTools(aiProvider); const tools = getProviderTools(provider);
return Environment.getInfoToolsBlockText(tools.map(t => t.function.name)); return Environment.getInfoToolsBlockText(tools.map(t => t.function.name));
}; };
@@ -61,13 +41,27 @@ export class Info extends Command {
}); });
}; };
const provider = await resolveEffectiveAiProviderForUser(msg.from.id);
// const aiProvidersLength = Object.keys(AiProvider).filter(key => isNaN(Number(key))).length;
const aiProviders = Object.keys(AiProvider).map(p => p.toLowerCase());
const finalText = [ const finalText = [
await getAiInfo(), `\`\`\`${Environment.runtimeProviderLabelText}`,
`${Environment.infoSupportedProvidersLabelText}: ${aiProviders.join(", ")}`,
`${Environment.runtimeProviderCurrentLabelText}: ${provider.toLowerCase()}`,
"```",
"",
`\`\`\`${Environment.runtimeCapabilitiesLabelText}`,
(await getFormattedCapabilities(provider)).join("\n"),
"```",
"",
await getToolsInfo(), await getToolsInfo(),
await getCommandsInfo() await getCommandsInfo()
].join("\n"); ].join("\n");
await replyToMessage({ await replyToMessage({
message: msg, message: msg,
text: prepareTelegramMarkdownV2(finalText, {mode: "final"}), text: prepareTelegramMarkdownV2(finalText, {mode: "final"}),
+1 -5
View File
@@ -16,10 +16,6 @@ export class MistralChat extends ChatCommand {
description = Environment.commandDescriptions.mistralChat; description = Environment.commandDescriptions.mistralChat;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
return this.executeMistral(msg, match?.[3] || ""); await runUnifiedAi({provider: AiProvider.MISTRAL, msg: msg, text: match?.[3] ?? "", stream: true});
}
async executeMistral(msg: Message, text: string, stream: boolean = true): Promise<void> {
await runUnifiedAi({provider: AiProvider.MISTRAL, msg, text, stream});
} }
} }
+7 -5
View File
@@ -16,10 +16,12 @@ export class OllamaChat extends ChatCommand {
description = Environment.commandDescriptions.ollamaChat; description = Environment.commandDescriptions.ollamaChat;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
return this.executeOllama(msg, match?.[3] || "", match?.[1]?.toLowerCase()?.startsWith("think")); await runUnifiedAi({
} provider: AiProvider.OLLAMA,
msg: msg,
async executeOllama(msg: Message, text: string, think: boolean = false): Promise<void> { text: match?.[3] ?? "",
await runUnifiedAi({provider: AiProvider.OLLAMA, msg, text, stream: true, think: think}); stream: true,
think: match?.[1]?.toLowerCase()?.startsWith("think")
});
} }
} }
+1 -5
View File
@@ -16,10 +16,6 @@ export class OpenAIChat extends ChatCommand {
description = Environment.commandDescriptions.openAiChat; description = Environment.commandDescriptions.openAiChat;
async execute(msg: Message, match?: RegExpExecArray): Promise<void> { async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
return this.executeOpenAI(msg, match?.[3] || ""); await runUnifiedAi({provider: AiProvider.OPENAI, msg: msg, text: match?.[3] ?? "", stream: true});
}
async executeOpenAI(msg: Message, text: string, stream: boolean = true): Promise<void> {
await runUnifiedAi({provider: AiProvider.OPENAI, msg, text, stream});
} }
} }
+719 -190
View File
@@ -330,147 +330,573 @@ export class Environment {
return Localization.textArray(key, params, fallback); return Localization.textArray(key, params, fallback);
} }
static get errorText() { return this.text("errorText", "⚠️ An error occurred."); } static get errorText() {
static get waitThinkText() { return this.text("waitThinkText", "⏳ Let me think..."); } return this.text("errorText", "⚠️ An error occurred.");
static get analyzingPictureText() { return this.text("analyzingPictureText", "🔍 Analyzing the image..."); } }
static get analyzingPicturesText() { return this.text("analyzingPicturesText", "🔍 Analyzing the images..."); }
static get reasoningText() { return this.text("reasoningText", "🤔 Reasoning..."); }
static get transcribingAudioText() { return this.text("transcribingAudioText", "🦻 Transcribing audio..."); }
static get genImageText() { return this.text("genImageText", "👨‍🎨 Generating an image..."); }
static get cancelText() { return this.text("cancelText", "❌ Cancel"); } static get waitThinkText() {
static get regenerateText() { return this.text("regenerateText", "🔄 Regenerate"); } return this.text("waitThinkText", "⏳ Let me think...");
static get aiCancelCallbackText() { return this.text("aiCancelCallbackText", "Cancel AI generation"); } }
static get aiRegenerateCallbackText() { return this.text("aiRegenerateCallbackText", "Regenerate AI response"); }
static get userSettingsCallbackText() { return this.text("userSettingsCallbackText", "User settings"); } static get analyzingPictureText() {
static get noAccessText() { return this.text("noAccessText", "No access"); } return this.text("analyzingPictureText", "🔍 Analyzing the image...");
static get notBotCreatorText() { return this.text("notBotCreatorText", "You are not the bot creator."); } }
static get notBotAdministratorText() { return this.text("notBotAdministratorText", "You are not a bot administrator."); }
static get notAChatText() { return this.text("notAChatText", "This is not a chat."); } static get analyzingPicturesText() {
static get notChatAdministratorText() { return this.text("notChatAdministratorText", "You are not a chat administrator."); } return this.text("analyzingPicturesText", "🔍 Analyzing the images...");
static get botNotChatAdministratorText() { return this.text("botNotChatAdministratorText", "The bot is not a chat administrator."); } }
static get replyRequiredText() { return this.text("replyRequiredText", "A reply to a message is required."); }
static get onlyOriginalAuthorText() { return this.text("onlyOriginalAuthorText", "Only the author of the original message can perform this action."); } static get reasoningText() {
static get dockerContainerLabelText() { return this.text("dockerContainerLabelText", "Docker container"); } return this.text("reasoningText", "🤔 Reasoning...");
static get processLabelText() { return this.text("processLabelText", "Process"); } }
static get systemLabelText() { return this.text("systemLabelText", "System"); }
static get systemInfoOsLabelText() { return this.text("systemInfoOsLabelText", "OS"); } static get transcribingAudioText() {
static get systemInfoRuntimeLabelText() { return this.text("systemInfoRuntimeLabelText", "RUNTIME"); } return this.text("transcribingAudioText", "🦻 Transcribing audio...");
static get systemInfoDockerLabelText() { return this.text("systemInfoDockerLabelText", "DOCKER"); } }
static get systemInfoCpuLabelText() { return this.text("systemInfoCpuLabelText", "CPU"); }
static get systemInfoRamLabelText() { return this.text("systemInfoRamLabelText", "RAM"); } static get genImageText() {
static get systemInfoCpuCoresText() { return this.text("systemInfoCpuCoresText", "cores"); } return this.text("genImageText", "👨‍🎨 Generating an image...");
static get systemInfoCpuThreadsText() { return this.text("systemInfoCpuThreadsText", "threads"); } }
static get idChatLabelText() { return this.text("idChatLabelText", "chat id"); }
static get idFromLabelText() { return this.text("idFromLabelText", "from id"); } static get cancelText() {
static get idReplyLabelText() { return this.text("idReplyLabelText", "reply id"); } return this.text("cancelText", "❌ Cancel");
static get runtimeProviderLabelText() { return this.text("runtimeProviderLabelText", "provider"); } }
static get runtimeModelLabelText() { return this.text("runtimeModelLabelText", "model"); }
static get runtimeCapabilitiesLabelText() { return this.text("runtimeCapabilitiesLabelText", "capabilities"); } static get regenerateText() {
static get runtimeExternalLabelText() { return this.text("runtimeExternalLabelText", "external"); } return this.text("regenerateText", "🔄 Regenerate");
static get runtimeCapabilityVisionText() { return this.text("runtimeCapabilityVisionText", "vision / image input"); } }
static get runtimeCapabilityOcrText() { return this.text("runtimeCapabilityOcrText", "ocr"); }
static get runtimeCapabilityThinkingText() { return this.text("runtimeCapabilityThinkingText", "thinking / reasoning"); } static get aiCancelCallbackText() {
static get runtimeCapabilityExtendedThinkingText() { return this.text("runtimeCapabilityExtendedThinkingText", "leveled thinking / reasoning"); } return this.text("aiCancelCallbackText", "Cancel AI generation");
static get runtimeCapabilityToolsText() { return this.text("runtimeCapabilityToolsText", "tools / function calling"); } }
static get runtimeCapabilityAudioText() { return this.text("runtimeCapabilityAudioText", "audio input"); }
static get runtimeCapabilitySpeechToTextText() { return this.text("runtimeCapabilitySpeechToTextText", "speech-to-text"); } static get aiRegenerateCallbackText() {
static get runtimeCapabilityTextToSpeechText() { return this.text("runtimeCapabilityTextToSpeechText", "text-to-speech"); } return this.text("aiRegenerateCallbackText", "Regenerate AI response");
static get runtimeCapabilityDocumentsText() { return this.text("runtimeCapabilityDocumentsText", "documents / rag"); } }
static get runtimeCapabilityOutputImagesText() { return this.text("runtimeCapabilityOutputImagesText", "image gen / image output"); }
static get infoAiBlockLabelText() { return this.text("infoAiBlockLabelText", "AI"); } static get userSettingsCallbackText() {
static get infoSupportedProvidersLabelText() { return this.text("infoSupportedProvidersLabelText", "supported providers"); } return this.text("userSettingsCallbackText", "User settings");
static get infoToolsBlockLabelText() { return this.text("infoToolsBlockLabelText", "tools"); } }
static get infoCountLabelText() { return this.text("infoCountLabelText", "count"); }
static get infoCommandsBlockLabelText() { return this.text("infoCommandsBlockLabelText", "commands"); } static get noAccessText() {
static get infoPublicLabelText() { return this.text("infoPublicLabelText", "public"); } return this.text("noAccessText", "No access");
static get infoPrivateLabelText() { return this.text("infoPrivateLabelText", "private"); } }
static get infoChatLabelText() { return this.text("infoChatLabelText", "chat"); }
static get infoCallbackLabelText() { return this.text("infoCallbackLabelText", "callback"); } static get notBotCreatorText() {
static get commandsHeaderText() { return this.text("commandsHeaderText", "Commands:\n\n"); } return this.text("notBotCreatorText", "You are not the bot creator.");
static get sentCommandsInDmText() { return this.text("sentCommandsInDmText", "Sent commands in DM 😎"); } }
static get couldNotSendCommandsInDmText() { return this.text("couldNotSendCommandsInDmText", "Could not send commands in DM ☹️\nSending them here instead"); }
static get administratorsHeaderText() { return this.text("administratorsHeaderText", "*Administrators*:\n\n"); } static get notBotAdministratorText() {
static get noUserInfoText() { return this.text("noUserInfoText", "No user information"); } return this.text("notBotAdministratorText", "You are not a bot administrator.");
static get useLeaveCommandText() { return this.text("useLeaveCommandText", "Use /leave"); } }
static get databaseBackupCaption() { return this.text("databaseBackupCaption", "Database backup"); }
static get databaseBackupSentText() { return this.text("databaseBackupSentText", "Successfully sent to the creator in DM!"); } static get notAChatText() {
static get noChoicesText() { return this.text("noChoicesText", "Nothing to choose from"); } return this.text("notAChatText", "This is not a chat.");
static get qrCodeMissingTextText() { return this.text("qrCodeMissingTextText", "No text found for QR code generation."); } }
static get quoteMissingTextText() { return this.text("quoteMissingTextText", "Could not find text in the message 😢"); }
static get quoteBuildFailedText() { return this.text("quoteBuildFailedText", "Could not build the quote 😢"); } static get notChatAdministratorText() {
static get speechToTextInstructionText() { return this.text("speechToTextInstructionText", "Send audio/voice/video-note or reply with /stt to a message containing audio."); } return this.text("notChatAdministratorText", "You are not a chat administrator.");
static get speechToTextEmptyResultText() { return this.text("speechToTextEmptyResultText", "Speech-to-text did not return transcription text."); } }
static get textToSpeechInstructionText() { return this.text("textToSpeechInstructionText", "Send text after the command or reply with /tts to a message containing text."); }
static get titleMissingText() { return this.text("titleMissingText", "Could not find a title..."); } static get botNotChatAdministratorText() {
static get betterFallbackText() { return this.text("betterFallbackText", "Better"); } return this.text("botNotChatAdministratorText", "The bot is not a chat administrator.");
static get pongText() { return this.text("pongText", "pong"); } }
static get variableNotDefinedText() { return this.text("variableNotDefinedText", "variable is not defined"); }
static get evaluationVariableNotDefinedText() { return this.text("evaluationVariableNotDefinedText", "Variable not defined"); } static get replyRequiredText() {
static get defaultTestAnswerText() { return this.text("defaultTestAnswerText", "a"); } return this.text("replyRequiredText", "A reply to a message is required.");
static get prefixFallbackText() { return this.text("prefixFallbackText", "?"); } }
static get searchResultsHeaderText() { return this.text("searchResultsHeaderText", "Results:\n\n"); }
static get modelListHeaderText() { return this.text("modelListHeaderText", "Available models:\n\n"); } static get onlyOriginalAuthorText() {
static get modelListLoadFailedText() { return this.text("modelListLoadFailedText", "Could not load the model list"); } return this.text("onlyOriginalAuthorText", "Only the author of the original message can perform this action.");
static get noCurrentModelText() { return this.text("noCurrentModelText", "Model is not set. Use one of the listed values."); } }
static get unsupportedAttachmentText() { return this.text("unsupportedAttachmentText", "This attachment type is not supported."); }
static get attachmentMissingFromCacheText() { return this.text("attachmentMissingFromCacheText", "Attachment file is missing from cache."); } static get dockerContainerLabelText() {
static get couldNotIdentifyUserForSpeechToTextText() { return this.text("couldNotIdentifyUserForSpeechToTextText", "Could not identify the user for speech-to-text."); } return this.text("dockerContainerLabelText", "Docker container");
static get missingTranscriptionFileText() { return this.text("missingTranscriptionFileText", "Unable to prepare the audio file for transcription."); } }
static get transcriptionFailedText() { return this.text("transcriptionFailedText", "Could not transcribe the audio."); }
static get imageGenUnsupportedFilesText() { return this.text("imageGenUnsupportedFilesText", "Image generation does not support files in this mode."); } static get processLabelText() {
static get unsupportedDocumentProviderText() { return this.text("unsupportedDocumentProviderText", "This provider does not support attached documents."); } return this.text("processLabelText", "Process");
static get mistralPdfOnlyText() { return this.text("mistralPdfOnlyText", "Mistral currently supports only PDF documents."); } }
static get mistralDocumentUploadFailedText() { return this.text("mistralDocumentUploadFailedText", "Could not upload the document to Mistral."); }
static get documentContentLabelText() { return this.text("documentContentLabelText", "Document content"); } static get systemLabelText() {
static get mistralLibraryIdMissingText() { return this.text("mistralLibraryIdMissingText", "Mistral did not return a temporary document library id."); } return this.text("systemLabelText", "System");
static get documentsUnifiedRunnerUnsupportedText() { return this.text("documentsUnifiedRunnerUnsupportedText", "Documents in the unified runner are currently handled only by Ollama RAG and Mistral."); } }
static get zipCentralDirectoryNotFoundText() { return this.text("zipCentralDirectoryNotFoundText", "ZIP archive is corrupted: central directory was not found."); }
static get zipInvalidCentralDirectoryText() { return this.text("zipInvalidCentralDirectoryText", "ZIP archive is corrupted: invalid central directory."); } static get systemInfoOsLabelText() {
static get tarFileTooLargeText() { return this.text("tarFileTooLargeText", "TAR contains a file that is too large."); } return this.text("systemInfoOsLabelText", "OS");
static get tarInvalidEntrySizeText() { return this.text("tarInvalidEntrySizeText", "TAR archive is corrupted: invalid entry size."); } }
static get tarEntryExceedsBoundsText() { return this.text("tarEntryExceedsBoundsText", "TAR archive is corrupted: entry exceeds file bounds."); }
static get docxDocumentXmlMissingText() { return this.text("docxDocumentXmlMissingText", "DOCX does not contain word/document.xml."); } static get systemInfoRuntimeLabelText() {
static get localRagEmbeddingModelRequiredText() { return this.text("localRagEmbeddingModelRequiredText", "Local RAG requires OLLAMA_EMBEDDING_MODEL, for example nomic-embed-text."); } return this.text("systemInfoRuntimeLabelText", "RUNTIME");
static get localRagChunksBuildFailedText() { return this.text("localRagChunksBuildFailedText", "Could not build chunks for local RAG."); } }
static get localRagNoSuitableFragmentsText() { return this.text("localRagNoSuitableFragmentsText", "Local RAG did not find suitable document fragments."); }
static get unsupportedAiProviderText() { return this.text("unsupportedAiProviderText", "Unsupported AI provider."); } static get systemInfoDockerLabelText() {
static get noSupportedTranscriptionProviderText() { return this.text("noSupportedTranscriptionProviderText", "No supported speech-to-text provider is configured."); } return this.text("systemInfoDockerLabelText", "DOCKER");
static get noSupportedTextToSpeechProviderText() { return this.text("noSupportedTextToSpeechProviderText", "No supported text-to-speech provider is configured."); } }
static get noSpeechToTextProviderForAccessText() { return this.text("noSpeechToTextProviderForAccessText", "No speech-to-text providers are configured for your access level."); }
static get noTextToSpeechProviderForAccessText() { return this.text("noTextToSpeechProviderForAccessText", "No text-to-speech providers are configured for your access level."); } static get systemInfoCpuLabelText() {
static get geminiSpeechToTextUnsupportedText() { return this.text("geminiSpeechToTextUnsupportedText", "Gemini does not support speech-to-text right now."); } return this.text("systemInfoCpuLabelText", "CPU");
static get geminiTextToSpeechUnsupportedText() { return this.text("geminiTextToSpeechUnsupportedText", "Gemini does not support text-to-speech right now."); } }
static get ollamaTextToSpeechUnsupportedText() { return this.text("ollamaTextToSpeechUnsupportedText", "Ollama does not support text-to-speech right now."); }
static get ollamaSpeechToTextModelRequiredText() { return this.text("ollamaSpeechToTextModelRequiredText", "Ollama speech-to-text requires OLLAMA_AUDIO_MODEL=gemma4:e2b or OLLAMA_AUDIO_MODEL=gemma4:e4b."); } static get systemInfoRamLabelText() {
static get noTextToSynthesizeText() { return this.text("noTextToSynthesizeText", "No text to synthesize."); } return this.text("systemInfoRamLabelText", "RAM");
static get mistralTtsNoAudioDataText() { return this.text("mistralTtsNoAudioDataText", "Mistral TTS did not return audioData."); } }
static get speechFileTooLargeText() { return this.text("speechFileTooLargeText", "The speech file is larger than 50 MB and cannot be sent."); }
static get userSettingsTitle() { return this.text("userSettingsTitle", "User Settings"); } static get systemInfoCpuCoresText() {
static get userSettingsAiProviderSelectionTitle() { return this.text("userSettingsAiProviderSelectionTitle", "AI Provider Selection"); } return this.text("systemInfoCpuCoresText", "cores");
static get userSettingsInterfaceLanguageSelectionTitle() { return this.text("userSettingsInterfaceLanguageSelectionTitle", "Interface Language Selection"); } }
static get userSettingsResponseLanguageSelectionTitle() { return this.text("userSettingsResponseLanguageSelectionTitle", "Response Language Selection"); }
static get userSettingsContextSizeSelectionTitle() { return this.text("userSettingsContextSizeSelectionTitle", "Context Size Selection"); } static get systemInfoCpuThreadsText() {
static get userSettingsVoiceModeSelectionTitle() { return this.text("userSettingsVoiceModeSelectionTitle", "Voice Message Mode Selection"); } return this.text("systemInfoCpuThreadsText", "threads");
static get userSettingsTierLabel() { return this.text("userSettingsTierLabel", "Tier"); } }
static get userSettingsAiProviderLabel() { return this.text("userSettingsAiProviderLabel", "AI provider"); }
static get userSettingsInterfaceLanguageLabel() { return this.text("userSettingsInterfaceLanguageLabel", "Interface language"); } static get idChatLabelText() {
static get userSettingsResponseLanguageLabel() { return this.text("userSettingsResponseLanguageLabel", "LLM response language"); } return this.text("idChatLabelText", "chat id");
static get userSettingsContextSizeLabel() { return this.text("userSettingsContextSizeLabel", "Context size"); } }
static get userSettingsVoiceModeLabel() { return this.text("userSettingsVoiceModeLabel", "Voice messages"); }
static get userSettingsBackButtonText() { return this.text("userSettingsBackButtonText", "Back"); } static get idFromLabelText() {
static get userSettingsAiProviderButtonPrefix() { return this.text("userSettingsAiProviderButtonPrefix", "AI provider"); } return this.text("idFromLabelText", "from id");
static get userSettingsInterfaceLanguageButtonPrefix() { return this.text("userSettingsInterfaceLanguageButtonPrefix", "Interface language"); } }
static get userSettingsResponseLanguageButtonPrefix() { return this.text("userSettingsResponseLanguageButtonPrefix", "Response language"); }
static get userSettingsContextSizeButtonPrefix() { return this.text("userSettingsContextSizeButtonPrefix", "Context size"); } static get idReplyLabelText() {
static get userSettingsVoiceModeButtonPrefix() { return this.text("userSettingsVoiceModeButtonPrefix", "Voice messages"); } return this.text("idReplyLabelText", "reply id");
static get userSettingsCreatorTierText() { return this.text("userSettingsCreatorTierText", "Creator"); } }
static get userSettingsAdminTierText() { return this.text("userSettingsAdminTierText", "Admin"); }
static get userSettingsUserTierText() { return this.text("userSettingsUserTierText", "User"); } static get runtimeProviderLabelText() {
static get userSettingsSelectedPrefix() { return this.text("userSettingsSelectedPrefix", "✓ "); } return this.text("runtimeProviderLabelText", "provider");
static get userSettingsContextSizeDefaultText() { return this.text("userSettingsContextSizeDefaultText", "Default"); } }
static get userSettingsVoiceModeExecuteText() { return this.text("userSettingsVoiceModeExecuteText", "Run through AI"); }
static get userSettingsVoiceModeTranscriptText() { return this.text("userSettingsVoiceModeTranscriptText", "Show transcript only"); } static get runtimeProviderCurrentLabelText() {
return this.text("runtimeProviderCurrentLabelText", "current");
}
static get runtimeModelLabelText() {
return this.text("runtimeModelLabelText", "model");
}
static get runtimeCapabilitiesLabelText() {
return this.text("runtimeCapabilitiesLabelText", "capabilities");
}
static get runtimeExternalLabelText() {
return this.text("runtimeExternalLabelText", "external");
}
static get runtimeCapabilityChatText() {
return this.text("runtimeCapabilityChatText", "chat");
}
static get runtimeCapabilityVisionText() {
return this.text("runtimeCapabilityVisionText", "vision / image input");
}
static get runtimeCapabilityOcrText() {
return this.text("runtimeCapabilityOcrText", "ocr");
}
static get runtimeCapabilityThinkingText() {
return this.text("runtimeCapabilityThinkingText", "thinking / reasoning");
}
static get runtimeCapabilityExtendedThinkingText() {
return this.text("runtimeCapabilityExtendedThinkingText", "leveled thinking / reasoning");
}
static get runtimeCapabilityToolsText() {
return this.text("runtimeCapabilityToolsText", "tools / function calling");
}
static get runtimeCapabilityAudioText() {
return this.text("runtimeCapabilityAudioText", "audio input");
}
static get runtimeCapabilitySpeechToTextText() {
return this.text("runtimeCapabilitySpeechToTextText", "speech-to-text");
}
static get runtimeCapabilityTextToSpeechText() {
return this.text("runtimeCapabilityTextToSpeechText", "text-to-speech");
}
static get runtimeCapabilityDocumentsText() {
return this.text("runtimeCapabilityDocumentsText", "documents / rag");
}
static get runtimeCapabilityOutputImagesText() {
return this.text("runtimeCapabilityOutputImagesText", "image gen / image output");
}
static get infoAiBlockLabelText() {
return this.text("infoAiBlockLabelText", "AI");
}
static get infoSupportedProvidersLabelText() {
return this.text("infoSupportedProvidersLabelText", "providers");
}
static get infoToolsBlockLabelText() {
return this.text("infoToolsBlockLabelText", "tools");
}
static get infoCommandsBlockLabelText() {
return this.text("infoCommandsBlockLabelText", "commands");
}
static get infoPublicLabelText() {
return this.text("infoPublicLabelText", "public");
}
static get infoPrivateLabelText() {
return this.text("infoPrivateLabelText", "private");
}
static get infoChatLabelText() {
return this.text("infoChatLabelText", "chat");
}
static get infoCallbackLabelText() {
return this.text("infoCallbackLabelText", "callback");
}
static get commandsHeaderText() {
return this.text("commandsHeaderText", "Commands:\n\n");
}
static get sentCommandsInDmText() {
return this.text("sentCommandsInDmText", "Sent commands in DM 😎");
}
static get couldNotSendCommandsInDmText() {
return this.text("couldNotSendCommandsInDmText", "Could not send commands in DM ☹️\nSending them here instead");
}
static get administratorsHeaderText() {
return this.text("administratorsHeaderText", "*Administrators*:\n\n");
}
static get noUserInfoText() {
return this.text("noUserInfoText", "No user information");
}
static get useLeaveCommandText() {
return this.text("useLeaveCommandText", "Use /leave");
}
static get databaseBackupCaption() {
return this.text("databaseBackupCaption", "Database backup");
}
static get databaseBackupSentText() {
return this.text("databaseBackupSentText", "Successfully sent to the creator in DM!");
}
static get noChoicesText() {
return this.text("noChoicesText", "Nothing to choose from");
}
static get qrCodeMissingTextText() {
return this.text("qrCodeMissingTextText", "No text found for QR code generation.");
}
static get quoteMissingTextText() {
return this.text("quoteMissingTextText", "Could not find text in the message 😢");
}
static get quoteBuildFailedText() {
return this.text("quoteBuildFailedText", "Could not build the quote 😢");
}
static get speechToTextInstructionText() {
return this.text("speechToTextInstructionText", "Send audio/voice/video-note or reply with /stt to a message containing audio.");
}
static get speechToTextEmptyResultText() {
return this.text("speechToTextEmptyResultText", "Speech-to-text did not return transcription text.");
}
static get textToSpeechInstructionText() {
return this.text("textToSpeechInstructionText", "Send text after the command or reply with /tts to a message containing text.");
}
static get titleMissingText() {
return this.text("titleMissingText", "Could not find a title...");
}
static get betterFallbackText() {
return this.text("betterFallbackText", "Better");
}
static get pongText() {
return this.text("pongText", "pong");
}
static get variableNotDefinedText() {
return this.text("variableNotDefinedText", "variable is not defined");
}
static get evaluationVariableNotDefinedText() {
return this.text("evaluationVariableNotDefinedText", "Variable not defined");
}
static get defaultTestAnswerText() {
return this.text("defaultTestAnswerText", "a");
}
static get prefixFallbackText() {
return this.text("prefixFallbackText", "?");
}
static get searchResultsHeaderText() {
return this.text("searchResultsHeaderText", "Results:\n\n");
}
static get modelListHeaderText() {
return this.text("modelListHeaderText", "Available models:\n\n");
}
static get modelListLoadFailedText() {
return this.text("modelListLoadFailedText", "Could not load the model list");
}
static get noCurrentModelText() {
return this.text("noCurrentModelText", "Model is not set. Use one of the listed values.");
}
static get unsupportedAttachmentText() {
return this.text("unsupportedAttachmentText", "This attachment type is not supported.");
}
static get attachmentMissingFromCacheText() {
return this.text("attachmentMissingFromCacheText", "Attachment file is missing from cache.");
}
static get couldNotIdentifyUserForSpeechToTextText() {
return this.text("couldNotIdentifyUserForSpeechToTextText", "Could not identify the user for speech-to-text.");
}
static get missingTranscriptionFileText() {
return this.text("missingTranscriptionFileText", "Unable to prepare the audio file for transcription.");
}
static get transcriptionFailedText() {
return this.text("transcriptionFailedText", "Could not transcribe the audio.");
}
static get imageGenUnsupportedFilesText() {
return this.text("imageGenUnsupportedFilesText", "Image generation does not support files in this mode.");
}
static get unsupportedDocumentProviderText() {
return this.text("unsupportedDocumentProviderText", "This provider does not support attached documents.");
}
static get mistralPdfOnlyText() {
return this.text("mistralPdfOnlyText", "Mistral currently supports only PDF documents.");
}
static get mistralDocumentUploadFailedText() {
return this.text("mistralDocumentUploadFailedText", "Could not upload the document to Mistral.");
}
static get documentContentLabelText() {
return this.text("documentContentLabelText", "Document content");
}
static get mistralLibraryIdMissingText() {
return this.text("mistralLibraryIdMissingText", "Mistral did not return a temporary document library id.");
}
static get documentsUnifiedRunnerUnsupportedText() {
return this.text("documentsUnifiedRunnerUnsupportedText", "Documents in the unified runner are currently handled only by Ollama RAG and Mistral.");
}
static get zipCentralDirectoryNotFoundText() {
return this.text("zipCentralDirectoryNotFoundText", "ZIP archive is corrupted: central directory was not found.");
}
static get zipInvalidCentralDirectoryText() {
return this.text("zipInvalidCentralDirectoryText", "ZIP archive is corrupted: invalid central directory.");
}
static get tarFileTooLargeText() {
return this.text("tarFileTooLargeText", "TAR contains a file that is too large.");
}
static get tarInvalidEntrySizeText() {
return this.text("tarInvalidEntrySizeText", "TAR archive is corrupted: invalid entry size.");
}
static get tarEntryExceedsBoundsText() {
return this.text("tarEntryExceedsBoundsText", "TAR archive is corrupted: entry exceeds file bounds.");
}
static get docxDocumentXmlMissingText() {
return this.text("docxDocumentXmlMissingText", "DOCX does not contain word/document.xml.");
}
static get localRagEmbeddingModelRequiredText() {
return this.text("localRagEmbeddingModelRequiredText", "Local RAG requires OLLAMA_EMBEDDING_MODEL, for example nomic-embed-text.");
}
static get localRagChunksBuildFailedText() {
return this.text("localRagChunksBuildFailedText", "Could not build chunks for local RAG.");
}
static get localRagNoSuitableFragmentsText() {
return this.text("localRagNoSuitableFragmentsText", "Local RAG did not find suitable document fragments.");
}
static get unsupportedAiProviderText() {
return this.text("unsupportedAiProviderText", "Unsupported AI provider.");
}
static get noSupportedTranscriptionProviderText() {
return this.text("noSupportedTranscriptionProviderText", "No supported speech-to-text provider is configured.");
}
static get noSupportedTextToSpeechProviderText() {
return this.text("noSupportedTextToSpeechProviderText", "No supported text-to-speech provider is configured.");
}
static get noSpeechToTextProviderForAccessText() {
return this.text("noSpeechToTextProviderForAccessText", "No speech-to-text providers are configured for your access level.");
}
static get noTextToSpeechProviderForAccessText() {
return this.text("noTextToSpeechProviderForAccessText", "No text-to-speech providers are configured for your access level.");
}
static get geminiSpeechToTextUnsupportedText() {
return this.text("geminiSpeechToTextUnsupportedText", "Gemini does not support speech-to-text right now.");
}
static get geminiTextToSpeechUnsupportedText() {
return this.text("geminiTextToSpeechUnsupportedText", "Gemini does not support text-to-speech right now.");
}
static get ollamaTextToSpeechUnsupportedText() {
return this.text("ollamaTextToSpeechUnsupportedText", "Ollama does not support text-to-speech right now.");
}
static get ollamaSpeechToTextModelRequiredText() {
return this.text("ollamaSpeechToTextModelRequiredText", "Ollama speech-to-text requires OLLAMA_AUDIO_MODEL=gemma4:e2b or OLLAMA_AUDIO_MODEL=gemma4:e4b.");
}
static get noTextToSynthesizeText() {
return this.text("noTextToSynthesizeText", "No text to synthesize.");
}
static get mistralTtsNoAudioDataText() {
return this.text("mistralTtsNoAudioDataText", "Mistral TTS did not return audioData.");
}
static get speechFileTooLargeText() {
return this.text("speechFileTooLargeText", "The speech file is larger than 50 MB and cannot be sent.");
}
static get userSettingsTitle() {
return this.text("userSettingsTitle", "User Settings");
}
static get userSettingsAiProviderSelectionTitle() {
return this.text("userSettingsAiProviderSelectionTitle", "AI Provider Selection");
}
static get userSettingsInterfaceLanguageSelectionTitle() {
return this.text("userSettingsInterfaceLanguageSelectionTitle", "Interface Language Selection");
}
static get userSettingsResponseLanguageSelectionTitle() {
return this.text("userSettingsResponseLanguageSelectionTitle", "Response Language Selection");
}
static get userSettingsContextSizeSelectionTitle() {
return this.text("userSettingsContextSizeSelectionTitle", "Context Size Selection");
}
static get userSettingsVoiceModeSelectionTitle() {
return this.text("userSettingsVoiceModeSelectionTitle", "Voice Message Mode Selection");
}
static get userSettingsTierLabel() {
return this.text("userSettingsTierLabel", "Tier");
}
static get userSettingsAiProviderLabel() {
return this.text("userSettingsAiProviderLabel", "AI provider");
}
static get userSettingsInterfaceLanguageLabel() {
return this.text("userSettingsInterfaceLanguageLabel", "Interface language");
}
static get userSettingsResponseLanguageLabel() {
return this.text("userSettingsResponseLanguageLabel", "LLM response language");
}
static get userSettingsContextSizeLabel() {
return this.text("userSettingsContextSizeLabel", "Context size");
}
static get userSettingsVoiceModeLabel() {
return this.text("userSettingsVoiceModeLabel", "Voice messages");
}
static get userSettingsBackButtonText() {
return this.text("userSettingsBackButtonText", "Back");
}
static get userSettingsAiProviderButtonPrefix() {
return this.text("userSettingsAiProviderButtonPrefix", "AI provider");
}
static get userSettingsInterfaceLanguageButtonPrefix() {
return this.text("userSettingsInterfaceLanguageButtonPrefix", "Interface language");
}
static get userSettingsResponseLanguageButtonPrefix() {
return this.text("userSettingsResponseLanguageButtonPrefix", "Response language");
}
static get userSettingsContextSizeButtonPrefix() {
return this.text("userSettingsContextSizeButtonPrefix", "Context size");
}
static get userSettingsVoiceModeButtonPrefix() {
return this.text("userSettingsVoiceModeButtonPrefix", "Voice messages");
}
static get userSettingsCreatorTierText() {
return this.text("userSettingsCreatorTierText", "Creator");
}
static get userSettingsAdminTierText() {
return this.text("userSettingsAdminTierText", "Admin");
}
static get userSettingsUserTierText() {
return this.text("userSettingsUserTierText", "User");
}
static get userSettingsSelectedPrefix() {
return this.text("userSettingsSelectedPrefix", "✓ ");
}
static get userSettingsContextSizeDefaultText() {
return this.text("userSettingsContextSizeDefaultText", "Default");
}
static get userSettingsContextSizeMaxText() {
return this.text("userSettingsContextSizeMaxText", "Max");
}
static get userSettingsVoiceModeExecuteText() {
return this.text("userSettingsVoiceModeExecuteText", "Run through AI");
}
static get userSettingsVoiceModeTranscriptText() {
return this.text("userSettingsVoiceModeTranscriptText", "Show transcript only");
}
static commandTitles = { static commandTitles = {
ae: "/ae", ae: "/ae",
@@ -605,9 +1031,17 @@ export class Environment {
return this.text("getCancelledText", "{provider}\n❌ Generation cancelled.", {provider}); return this.text("getCancelledText", "{provider}\n❌ Generation cancelled.", {provider});
} }
static get startingImageGenText() { return this.text("startingImageGenText", "🌈 Starting image generation..."); } static get startingImageGenText() {
static get imageGenText() { return this.text("imageGenText", "🌈 Generating image..."); } return this.text("startingImageGenText", "🌈 Starting image generation...");
static get finalizingImageGenText() { return this.text("finalizingImageGenText", "🌈 Finalizing image generation..."); } }
static get imageGenText() {
return this.text("imageGenText", "🌈 Generating image...");
}
static get finalizingImageGenText() {
return this.text("finalizingImageGenText", "🌈 Finalizing image generation...");
}
static getPartialImageGenText(iteration: number, total: number): string { static getPartialImageGenText(iteration: number, total: number): string {
return this.text("getPartialImageGenText", "🌈 Generating image ({iteration}/{total})...", {iteration, total}); return this.text("getPartialImageGenText", "🌈 Generating image ({iteration}/{total})...", {iteration, total});
@@ -694,20 +1128,9 @@ export class Environment {
].join("\n"); ].join("\n");
} }
static getInfoAiBlockText(supportedProvidersLength: number, modelInfo: string): string {
return [
`\`\`\`${this.infoAiBlockLabelText}`,
`${this.infoSupportedProvidersLabelText}: ${supportedProvidersLength}`,
"",
modelInfo,
"```",
].join("\n");
}
static getInfoToolsBlockText(toolNames: string[]): string { static getInfoToolsBlockText(toolNames: string[]): string {
return [ return [
`\`\`\`${this.infoToolsBlockLabelText}`, `\`\`\`${this.infoToolsBlockLabelText}`,
`${this.infoCountLabelText}: ${toolNames.length}`,
toolNames.map(name => `- ${name}`).join("\n"), toolNames.map(name => `- ${name}`).join("\n"),
"```", "```",
].join("\n"); ].join("\n");
@@ -780,7 +1203,10 @@ export class Environment {
} }
static getTelegramFileTooLargeText(fileName: string, maxSizeMb: number): string { static getTelegramFileTooLargeText(fileName: string, maxSizeMb: number): string {
return this.text("getTelegramFileTooLargeText", "File {fileName} is larger than {maxSizeMb} MB and cannot be sent.", {fileName, maxSizeMb}); return this.text("getTelegramFileTooLargeText", "File {fileName} is larger than {maxSizeMb} MB and cannot be sent.", {
fileName,
maxSizeMb
});
} }
static getUserIsNowAdminText(name: string): string { static getUserIsNowAdminText(name: string): string {
@@ -799,23 +1225,73 @@ export class Environment {
return this.text("getUserWasNotAdminText", "{name} was not an admin 🤔", {name}); return this.text("getUserWasNotAdminText", "{name} was not an admin 🤔", {name});
} }
static get botCannotMakeItselfAdminText() { return this.text("botCannotMakeItselfAdminText", "The bot cannot make itself an admin"); } static get botCannotMakeItselfAdminText() {
static get botCreatorAlreadyAdminText() { return this.text("botCreatorAlreadyAdminText", "The bot creator is already an admin"); } return this.text("botCannotMakeItselfAdminText", "The bot cannot make itself an admin");
static get botCannotRemoveItselfFromAdminsText() { return this.text("botCannotRemoveItselfFromAdminsText", "The bot cannot remove itself from admins"); } }
static get botCreatorCannotStopBeingAdminText() { return this.text("botCreatorCannotStopBeingAdminText", "The bot creator cannot stop being an admin"); }
static get botWillNotBanCreatorText() { return this.text("botWillNotBanCreatorText", "The bot will not ban its creator."); } static get botCreatorAlreadyAdminText() {
static get botWillNotBanAdminsText() { return this.text("botWillNotBanAdminsText", "The bot will not ban its administrators."); } return this.text("botCreatorAlreadyAdminText", "The bot creator is already an admin");
static get botIsNotBannedByItselfText() { return this.text("botIsNotBannedByItselfText", "The bot is not banned by itself anyway."); } }
static get botCreatorNeverBannedText() { return this.text("botCreatorNeverBannedText", "The bot creator is not banned and never will be."); }
static get botAdminsNotBannedText() { return this.text("botAdminsNotBannedText", "Bot administrators are not banned anyway."); } static get botCannotRemoveItselfFromAdminsText() {
static get botWillNotIgnoreItselfText() { return this.text("botWillNotIgnoreItselfText", "The bot will not ignore itself."); } return this.text("botCannotRemoveItselfFromAdminsText", "The bot cannot remove itself from admins");
static get botWillNotIgnoreCreatorText() { return this.text("botWillNotIgnoreCreatorText", "The bot will not ignore its creator."); } }
static get botWillNotIgnoreAdminsText() { return this.text("botWillNotIgnoreAdminsText", "The bot will not ignore its administrators."); }
static get botIsNotIgnoredByItselfText() { return this.text("botIsNotIgnoredByItselfText", "The bot is not ignored by itself anyway."); } static get botCreatorCannotStopBeingAdminText() {
static get botCreatorNotIgnoredText() { return this.text("botCreatorNotIgnoredText", "The bot creator is not ignored and never will be."); } return this.text("botCreatorCannotStopBeingAdminText", "The bot creator cannot stop being an admin");
static get botAdminsNotIgnoredText() { return this.text("botAdminsNotIgnoredText", "Bot administrators are not ignored anyway."); } }
static get botAlreadyAlwaysListensToItselfText() { return this.text("botAlreadyAlwaysListensToItselfText", "The bot already always listens to itself"); }
static get botAlwaysListensToCreatorText() { return this.text("botAlwaysListensToCreatorText", "The bot always listens to its creator"); } static get botWillNotBanCreatorText() {
return this.text("botWillNotBanCreatorText", "The bot will not ban its creator.");
}
static get botWillNotBanAdminsText() {
return this.text("botWillNotBanAdminsText", "The bot will not ban its administrators.");
}
static get botIsNotBannedByItselfText() {
return this.text("botIsNotBannedByItselfText", "The bot is not banned by itself anyway.");
}
static get botCreatorNeverBannedText() {
return this.text("botCreatorNeverBannedText", "The bot creator is not banned and never will be.");
}
static get botAdminsNotBannedText() {
return this.text("botAdminsNotBannedText", "Bot administrators are not banned anyway.");
}
static get botWillNotIgnoreItselfText() {
return this.text("botWillNotIgnoreItselfText", "The bot will not ignore itself.");
}
static get botWillNotIgnoreCreatorText() {
return this.text("botWillNotIgnoreCreatorText", "The bot will not ignore its creator.");
}
static get botWillNotIgnoreAdminsText() {
return this.text("botWillNotIgnoreAdminsText", "The bot will not ignore its administrators.");
}
static get botIsNotIgnoredByItselfText() {
return this.text("botIsNotIgnoredByItselfText", "The bot is not ignored by itself anyway.");
}
static get botCreatorNotIgnoredText() {
return this.text("botCreatorNotIgnoredText", "The bot creator is not ignored and never will be.");
}
static get botAdminsNotIgnoredText() {
return this.text("botAdminsNotIgnoredText", "Bot administrators are not ignored anyway.");
}
static get botAlreadyAlwaysListensToItselfText() {
return this.text("botAlreadyAlwaysListensToItselfText", "The bot already always listens to itself");
}
static get botAlwaysListensToCreatorText() {
return this.text("botAlwaysListensToCreatorText", "The bot always listens to its creator");
}
static getUserBannedText(name: string): string { static getUserBannedText(name: string): string {
return this.text("getUserBannedText", "{name} banned 🚫", {name}); return this.text("getUserBannedText", "{name} banned 🚫", {name});
@@ -865,13 +1341,27 @@ export class Environment {
return this.text("getCoinResultText", "It landed on *{result}*", {result}); return this.text("getCoinResultText", "It landed on *{result}*", {result});
} }
static get coinHeadsText() { return this.text("coinHeadsText", "Heads"); } static get coinHeadsText() {
static get coinTailsText() { return this.text("coinTailsText", "Tails"); } return this.text("coinHeadsText", "Heads");
static get distortReplyInstructionText() { return this.text("distortReplyInstructionText", "Reply with /distort to a message containing an image (photo, document, or sticker).\nExample: /distort 16 80"); } }
static get distortMissingImageText() { return this.text("distortMissingImageText", "I do not see an image in the reply. Send a photo or image file."); }
static get coinTailsText() {
return this.text("coinTailsText", "Tails");
}
static get distortReplyInstructionText() {
return this.text("distortReplyInstructionText", "Reply with /distort to a message containing an image (photo, document, or sticker).\nExample: /distort 16 80");
}
static get distortMissingImageText() {
return this.text("distortMissingImageText", "I do not see an image in the reply. Send a photo or image file.");
}
static getDistortionReadyCaption(amp: number, wavelength: number): string { static getDistortionReadyCaption(amp: number, wavelength: number): string {
return this.text("getDistortionReadyCaption", "Distortion ready ✅ (amp={amp}, wavelength={wavelength})", {amp, wavelength}); return this.text("getDistortionReadyCaption", "Distortion ready ✅ (amp={amp}, wavelength={wavelength})", {
amp,
wavelength
});
} }
static getDistortFailedText(error: unknown): string { static getDistortFailedText(error: unknown): string {
@@ -929,7 +1419,10 @@ export class Environment {
} }
static getMistralDocumentProcessingFailedText(fileName: string, status: string): string { static getMistralDocumentProcessingFailedText(fileName: string, status: string): string {
return this.text("getMistralDocumentProcessingFailedText", "Mistral could not process document {fileName}: {status}", {fileName, status}); return this.text("getMistralDocumentProcessingFailedText", "Mistral could not process document {fileName}: {status}", {
fileName,
status
});
} }
static getMistralDocumentProcessingTimedOutText(fileName: string): string { static getMistralDocumentProcessingTimedOutText(fileName: string): string {
@@ -945,7 +1438,10 @@ export class Environment {
} }
static getZipUnsupportedCompressionMethodText(method: number, entryName: string): string { static getZipUnsupportedCompressionMethodText(method: number, entryName: string): string {
return this.text("getZipUnsupportedCompressionMethodText", "ZIP archive uses unsupported compression method {method} for {entryName}.", {method, entryName}); return this.text("getZipUnsupportedCompressionMethodText", "ZIP archive uses unsupported compression method {method} for {entryName}.", {
method,
entryName
});
} }
static getGzipUncompressedLimitText(maxBytes: number): string { static getGzipUncompressedLimitText(maxBytes: number): string {
@@ -1015,7 +1511,11 @@ export class Environment {
reason: error instanceof Error ? error.message : String(error), reason: error instanceof Error ? error.message : String(error),
}); });
} }
static get shutdownFallbackText() { return this.text("shutdownFallbackText", "..."); }
static get shutdownFallbackText() {
return this.text("shutdownFallbackText", "...");
}
static get shutdownSequenceTexts() { static get shutdownSequenceTexts() {
return this.textArray("shutdownSequenceTexts", [ return this.textArray("shutdownSequenceTexts", [
"well then, everyone", "well then, everyone",
@@ -1024,21 +1524,50 @@ export class Environment {
"all the best", "all the best",
]); ]);
} }
static get shutdownDoneText() { return this.text("shutdownDoneText", "*R.I.P*"); }
static get shutdownDoneText() {
return this.text("shutdownDoneText", "*R.I.P*");
}
static getWhenPrefixText(): string { static getWhenPrefixText(): string {
return this.text("getWhenPrefixText", "in "); return this.text("getWhenPrefixText", "in ");
} }
static get whenNowText() { return this.text("whenNowText", "right now"); } static get whenNowText() {
static get whenNeverText() { return this.text("whenNeverText", "never"); } return this.text("whenNowText", "right now");
static get whenYearUnitText() { return this.text("whenYearUnitText", "year"); } }
static get whenDayUnitText() { return this.text("whenDayUnitText", "day"); }
static get whenWeekUnitText() { return this.text("whenWeekUnitText", "week"); } static get whenNeverText() {
static get whenMonthUnitText() { return this.text("whenMonthUnitText", "month"); } return this.text("whenNeverText", "never");
static get whenHourUnitText() { return this.text("whenHourUnitText", "hour"); } }
static get whenMinuteUnitText() { return this.text("whenMinuteUnitText", "minute"); }
static get whenSecondUnitText() { return this.text("whenSecondUnitText", "second"); } static get whenYearUnitText() {
return this.text("whenYearUnitText", "year");
}
static get whenDayUnitText() {
return this.text("whenDayUnitText", "day");
}
static get whenWeekUnitText() {
return this.text("whenWeekUnitText", "week");
}
static get whenMonthUnitText() {
return this.text("whenMonthUnitText", "month");
}
static get whenHourUnitText() {
return this.text("whenHourUnitText", "hour");
}
static get whenMinuteUnitText() {
return this.text("whenMinuteUnitText", "minute");
}
static get whenSecondUnitText() {
return this.text("whenSecondUnitText", "second");
}
static getWhenDurationText(value: number, unit: string): string { static getWhenDurationText(value: number, unit: string): string {
const pluralUnit = value === 1 ? unit : this.text("getWhenPluralUnitText", "{unit}s", {unit}); const pluralUnit = value === 1 ? unit : this.text("getWhenPluralUnitText", "{unit}s", {unit});
+20 -11
View File
@@ -3,21 +3,18 @@ import {UserStore} from "./user-store";
import {AiProvider} from "../model/ai-provider"; import {AiProvider} from "../model/ai-provider";
import {StoredUser} from "../model/stored-user"; import {StoredUser} from "../model/stored-user";
import {resolveAiRuntimeTarget} from "../ai/ai-runtime-target"; import {resolveAiRuntimeTarget} from "../ai/ai-runtime-target";
import { import {DEFAULT_LANGUAGE_CHOICE, LanguageChoice, Localization,} from "./localization";
DEFAULT_LANGUAGE_CHOICE,
LanguageChoice,
Localization,
} from "./localization";
export const DEFAULT_AI_PROVIDER_CHOICE = "DEFAULT"; export const DEFAULT_AI_PROVIDER_CHOICE = "DEFAULT";
export const DEFAULT_AI_CONTEXT_SIZE_CHOICE = "DEFAULT"; export const DEFAULT_AI_CONTEXT_SIZE_CHOICE = "DEFAULT";
export const USER_AI_CONTEXT_SIZE_PRESETS = [64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 256000] as const; export const AI_CONTEXT_SIZE_MAX_CHOICE = "MAX";
export const MIN_USER_AI_CONTEXT_SIZE = 64; export const USER_AI_CONTEXT_SIZE_PRESETS = [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144] as const;
export const MIN_USER_AI_CONTEXT_SIZE = 1024;
export const MAX_USER_AI_CONTEXT_SIZE = 1_000_000; export const MAX_USER_AI_CONTEXT_SIZE = 1_000_000;
export const AI_VOICE_MODE_EXECUTE = "execute"; export const AI_VOICE_MODE_EXECUTE = "execute";
export const AI_VOICE_MODE_TRANSCRIPT = "transcript"; export const AI_VOICE_MODE_TRANSCRIPT = "transcript";
export type UserAiProviderChoice = AiProvider | typeof DEFAULT_AI_PROVIDER_CHOICE; export type UserAiProviderChoice = AiProvider | typeof DEFAULT_AI_PROVIDER_CHOICE;
export type UserAiContextSizeChoice = number | typeof DEFAULT_AI_CONTEXT_SIZE_CHOICE; export type UserAiContextSizeChoice = number | typeof DEFAULT_AI_CONTEXT_SIZE_CHOICE | typeof AI_CONTEXT_SIZE_MAX_CHOICE;
export type UserAiVoiceMode = typeof AI_VOICE_MODE_EXECUTE | typeof AI_VOICE_MODE_TRANSCRIPT; export type UserAiVoiceMode = typeof AI_VOICE_MODE_EXECUTE | typeof AI_VOICE_MODE_TRANSCRIPT;
export type UserInterfaceLanguage = LanguageChoice; export type UserInterfaceLanguage = LanguageChoice;
export type UserAiResponseLanguage = LanguageChoice; export type UserAiResponseLanguage = LanguageChoice;
@@ -65,7 +62,7 @@ export function getUserLanguageChoices(): string[] {
} }
export function getUserAiContextSizeChoices(): UserAiContextSizeChoice[] { export function getUserAiContextSizeChoices(): UserAiContextSizeChoice[] {
return [DEFAULT_AI_CONTEXT_SIZE_CHOICE, ...USER_AI_CONTEXT_SIZE_PRESETS]; return [DEFAULT_AI_CONTEXT_SIZE_CHOICE, ...USER_AI_CONTEXT_SIZE_PRESETS, AI_CONTEXT_SIZE_MAX_CHOICE];
} }
export function getUserAiVoiceModes(): UserAiVoiceMode[] { export function getUserAiVoiceModes(): UserAiVoiceMode[] {
@@ -129,11 +126,19 @@ export function normalizeAiContextSizeChoice(value: string | number | undefined
return DEFAULT_AI_CONTEXT_SIZE_CHOICE; return DEFAULT_AI_CONTEXT_SIZE_CHOICE;
} }
if (normalized === AI_CONTEXT_SIZE_MAX_CHOICE || lower === "max") {
return AI_CONTEXT_SIZE_MAX_CHOICE;
}
numericValue = Number(normalized); numericValue = Number(normalized);
} else { } else {
numericValue = value; numericValue = value;
} }
if (numericValue === -1) {
return AI_CONTEXT_SIZE_MAX_CHOICE;
}
if (!Number.isSafeInteger(numericValue) || numericValue < MIN_USER_AI_CONTEXT_SIZE || numericValue > MAX_USER_AI_CONTEXT_SIZE) { if (!Number.isSafeInteger(numericValue) || numericValue < MIN_USER_AI_CONTEXT_SIZE || numericValue > MAX_USER_AI_CONTEXT_SIZE) {
return undefined; return undefined;
} }
@@ -195,6 +200,10 @@ export function getContextSizeLabel(choice: UserAiContextSizeChoice): string {
return Environment.userSettingsContextSizeDefaultText; return Environment.userSettingsContextSizeDefaultText;
} }
if (choice === AI_CONTEXT_SIZE_MAX_CHOICE) {
return Environment.userSettingsContextSizeMaxText;
}
return Environment.getUserSettingsContextSizeText(choice); return Environment.getUserSettingsContextSizeText(choice);
} }
@@ -248,7 +257,7 @@ function shouldUpdateVoiceMode(user: StoredUser | null, mode: UserAiVoiceMode):
} }
function contextSizeChoiceToStored(choice: UserAiContextSizeChoice): number | undefined { function contextSizeChoiceToStored(choice: UserAiContextSizeChoice): number | undefined {
return choice === DEFAULT_AI_CONTEXT_SIZE_CHOICE ? undefined : choice; return choice === DEFAULT_AI_CONTEXT_SIZE_CHOICE ? undefined : choice === AI_CONTEXT_SIZE_MAX_CHOICE ? -1 : choice;
} }
export async function ensureValidUserAiSettings(userId: number): Promise<EffectiveUserAiSettings> { export async function ensureValidUserAiSettings(userId: number): Promise<EffectiveUserAiSettings> {
@@ -383,7 +392,7 @@ export async function setUserAiContextSizeChoice(
const settings = await ensureValidUserAiSettings(userId); const settings = await ensureValidUserAiSettings(userId);
const normalized = normalizeAiContextSizeChoice(choice); const normalized = normalizeAiContextSizeChoice(choice);
if (!normalized) { if (!normalized && normalized !== -1) {
return {ok: false, settings}; return {ok: false, settings};
} }
+1
View File
@@ -1,6 +1,7 @@
import {AiCapabilityInfo} from "./ai-capability-info"; import {AiCapabilityInfo} from "./ai-capability-info";
export class AiModelCapabilities { export class AiModelCapabilities {
chat: AiCapabilityInfo | undefined;
vision: AiCapabilityInfo | undefined; vision: AiCapabilityInfo | undefined;
ocr: AiCapabilityInfo | undefined; ocr: AiCapabilityInfo | undefined;
thinking: AiCapabilityInfo | undefined; thinking: AiCapabilityInfo | undefined;
+2 -14
View File
@@ -90,7 +90,8 @@ export function searchChatCommand(
botUsername: string | undefined = botUser.username botUsername: string | undefined = botUser.username
): Command | null { ): Command | null {
for (const command of commands) { for (const command of commands) {
const match = command.finalRegexp.exec(text); const finalRegexp = command.finalRegexp;
const match = finalRegexp.exec(text);
if (!match) continue; if (!match) continue;
const mentioned = match[2]?.toLowerCase(); const mentioned = match[2]?.toLowerCase();
@@ -2089,19 +2090,6 @@ export function photoPathByUniqueId(uniqueId: string): string {
return path.join(photoDir, uniqueId + ".jpg"); return path.join(photoDir, uniqueId + ".jpg");
} }
export function getCurrentModel(): string | undefined {
switch (Environment.DEFAULT_AI_PROVIDER) {
case AiProvider.OLLAMA:
return Environment.OLLAMA_CHAT_MODEL;
case AiProvider.GEMINI:
return Environment.GEMINI_MODEL;
case AiProvider.MISTRAL:
return Environment.MISTRAL_MODEL;
case AiProvider.OPENAI:
return Environment.OPENAI_MODEL;
}
}
export async function processMyChatMember(u: ChatMemberUpdated): Promise<void> { export async function processMyChatMember(u: ChatMemberUpdated): Promise<void> {
console.log("my_chat_member", u); console.log("my_chat_member", u);
} }