2082 lines
78 KiB
TypeScript
2082 lines
78 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import {parse as parseDotEnv} from "dotenv";
|
|
import {z} from "zod";
|
|
import {appLogger} from "../logging/logger.js";
|
|
import type {BoundaryValue, ErrorLike} from "./boundary-types";
|
|
|
|
import {Answers} from "../model/answers.js";
|
|
import {AiProvider} from "../model/ai-provider.js";
|
|
import {ImageHandleFallbackPolicy, ImageHandlePolicy, RateLimitFallbackPolicy} from "./policies.js";
|
|
import {ToolRankerFallbackPolicy} from "./policies.js";
|
|
import type {ToolCallData} from "../ai/unified-ai-runner.js";
|
|
import {PYTHON_INTERPRETER_TOOL_NAME} from "../ai/tools/python-interpretator.js";
|
|
import {Localization, type LocalizationParams} from "./localization.js";
|
|
|
|
function parseBooleanLike(value: string): boolean {
|
|
const normalized = value.trim().toLowerCase();
|
|
return ["true", "t", "y", "1"].includes(normalized);
|
|
}
|
|
|
|
type EnvRecord = Record<string, string>;
|
|
type StringEnumLike = Record<string, string>;
|
|
type StringEnumValue<T extends StringEnumLike> = T[keyof T];
|
|
|
|
function normalizeString(value: BoundaryValue): string | undefined {
|
|
if (typeof value !== "string") {
|
|
return undefined;
|
|
}
|
|
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
const optionalStringSchema = z
|
|
.preprocess(normalizeString, z.string().optional())
|
|
.optional()
|
|
.catch(undefined);
|
|
|
|
function stringWithDefaultSchema(defaultValue: string) {
|
|
return z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
return normalized ?? defaultValue;
|
|
}, z.string())
|
|
.default(defaultValue)
|
|
.catch(defaultValue);
|
|
}
|
|
|
|
function booleanWithDefaultSchema(defaultValue: boolean) {
|
|
return z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
|
|
if (normalized === undefined) {
|
|
return defaultValue;
|
|
}
|
|
|
|
return parseBooleanLike(normalized);
|
|
}, z.boolean())
|
|
.default(defaultValue)
|
|
.catch(defaultValue);
|
|
}
|
|
|
|
const optionalBooleanSchema = z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
return normalized === undefined ? undefined : parseBooleanLike(normalized);
|
|
}, z.boolean().optional())
|
|
.optional()
|
|
.catch(undefined);
|
|
|
|
function requiredStringSchema() {
|
|
return z
|
|
.preprocess(normalizeString, z.string().min(1));
|
|
}
|
|
|
|
function requiredPositiveIntSchema() {
|
|
return z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
|
|
if (normalized === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
const number = Number(normalized);
|
|
|
|
if (!Number.isSafeInteger(number) || number <= 0) {
|
|
return undefined;
|
|
}
|
|
|
|
return number;
|
|
}, z.number().int().positive());
|
|
}
|
|
|
|
function numberWithDefaultSchema(defaultValue: number) {
|
|
return z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
|
|
if (normalized === undefined) {
|
|
return defaultValue;
|
|
}
|
|
|
|
const number = Number(normalized);
|
|
return Number.isFinite(number) ? number : defaultValue;
|
|
}, z.number())
|
|
.default(defaultValue)
|
|
.catch(defaultValue);
|
|
}
|
|
|
|
function positiveIntWithDefaultSchema(defaultValue: number) {
|
|
return z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
|
|
if (normalized === undefined) {
|
|
return defaultValue;
|
|
}
|
|
|
|
const number = Number(normalized);
|
|
|
|
if (!Number.isSafeInteger(number) || number <= 0) {
|
|
return defaultValue;
|
|
}
|
|
|
|
return number;
|
|
}, z.number().int().positive())
|
|
.default(defaultValue)
|
|
.catch(defaultValue);
|
|
}
|
|
|
|
function enumWithDefaultSchema<T extends StringEnumLike>(
|
|
enumObject: T,
|
|
defaultValue: StringEnumValue<T>,
|
|
) {
|
|
const values = Object.values(enumObject) as StringEnumValue<T>[];
|
|
|
|
return z
|
|
.preprocess(value => {
|
|
const normalized = normalizeString(value as BoundaryValue);
|
|
|
|
if (normalized === undefined) {
|
|
return defaultValue;
|
|
}
|
|
|
|
return values.includes(normalized as StringEnumValue<T>)
|
|
? normalized
|
|
: defaultValue;
|
|
}, z.custom<StringEnumValue<T>>((value): value is StringEnumValue<T> => {
|
|
return typeof value === "string"
|
|
&& values.includes(value as StringEnumValue<T>);
|
|
}))
|
|
.default(defaultValue)
|
|
.catch(defaultValue);
|
|
}
|
|
|
|
const StartupEnvSchema = z.object({
|
|
BOT_TOKEN: requiredStringSchema(),
|
|
DATABASE_URL: optionalStringSchema,
|
|
DB_PATH: optionalStringSchema,
|
|
DATA_PATH: optionalStringSchema,
|
|
TEST_ENVIRONMENT: booleanWithDefaultSchema(false),
|
|
IS_DOCKER: optionalBooleanSchema,
|
|
});
|
|
|
|
const RuntimeEnvSchema = z.object({
|
|
CREATOR_ID: requiredPositiveIntSchema(),
|
|
BOT_PREFIX: stringWithDefaultSchema(""),
|
|
CHAT_IDS_WHITELIST: optionalStringSchema,
|
|
ONLY_FOR_CREATOR_MODE: booleanWithDefaultSchema(false),
|
|
ENABLE_UNSAFE_EVAL: booleanWithDefaultSchema(false),
|
|
MAX_PHOTO_SIZE: positiveIntWithDefaultSchema(1280),
|
|
PROCESS_LINKS: booleanWithDefaultSchema(false),
|
|
LOCALES_DIR: stringWithDefaultSchema("locales"),
|
|
|
|
RATE_LIMIT_FALLBACK_POLICY: enumWithDefaultSchema(
|
|
RateLimitFallbackPolicy,
|
|
RateLimitFallbackPolicy.NOTIFY_USER,
|
|
),
|
|
|
|
IMAGE_HANDLE_POLICY: enumWithDefaultSchema(
|
|
ImageHandlePolicy,
|
|
ImageHandlePolicy.HANDLE_IF_CAPABLE,
|
|
),
|
|
|
|
IMAGE_HANDLE_FALLBACK_POLICY: enumWithDefaultSchema(
|
|
ImageHandleFallbackPolicy,
|
|
ImageHandleFallbackPolicy.NOTIFY_USER,
|
|
),
|
|
|
|
TOOL_RANKER_FALLBACK_POLICY: enumWithDefaultSchema(
|
|
ToolRankerFallbackPolicy,
|
|
ToolRankerFallbackPolicy.ALL_TOOLS,
|
|
),
|
|
|
|
BRAVE_SEARCH_API_KEY: optionalStringSchema,
|
|
OPEN_WEATHER_MAP_API_KEY: optionalStringSchema,
|
|
|
|
FILE_TOOLS_ROOT_DIR: optionalStringSchema,
|
|
ENABLE_FS_TOOLS: optionalBooleanSchema,
|
|
|
|
DEFAULT_AI_PROVIDER: enumWithDefaultSchema(
|
|
AiProvider,
|
|
AiProvider.OLLAMA,
|
|
),
|
|
|
|
SYSTEM_PROMPT: optionalStringSchema,
|
|
RANKER_TOOL_PROMPT: optionalStringSchema,
|
|
USE_NAMES_IN_PROMPT: booleanWithDefaultSchema(false),
|
|
USE_SYSTEM_PROMPT: booleanWithDefaultSchema(true),
|
|
|
|
SEND_TIME_TOOK: optionalBooleanSchema,
|
|
|
|
ENABLE_PYTHON_INTERPRETER: optionalBooleanSchema,
|
|
|
|
OLLAMA_API_KEY: optionalStringSchema,
|
|
OLLAMA_ADDRESS: optionalStringSchema,
|
|
OLLAMA_CHAT_MODEL: stringWithDefaultSchema("gemma4:e4b"),
|
|
OLLAMA_IMAGE_MODEL: optionalStringSchema,
|
|
OLLAMA_THINK_MODEL: optionalStringSchema,
|
|
OLLAMA_AUDIO_MODEL: optionalStringSchema,
|
|
OLLAMA_EMBEDDING_MODEL: stringWithDefaultSchema("nomic-embed-text:latest"),
|
|
OLLAMA_RAG_CHUNK_SIZE: positiveIntWithDefaultSchema(1400),
|
|
OLLAMA_RAG_CHUNK_OVERLAP: positiveIntWithDefaultSchema(220),
|
|
OLLAMA_RAG_TOP_K: positiveIntWithDefaultSchema(8),
|
|
OLLAMA_RAG_MAX_CONTEXT_CHARS: positiveIntWithDefaultSchema(14000),
|
|
OLLAMA_RAG_MIN_SCORE: numberWithDefaultSchema(0.12),
|
|
OLLAMA_RAG_MAX_ARCHIVE_FILES: positiveIntWithDefaultSchema(200),
|
|
OLLAMA_RAG_MAX_ARCHIVE_BYTES: positiveIntWithDefaultSchema(50 * 1024 * 1024),
|
|
OLLAMA_RAG_MAX_ARCHIVE_DEPTH: positiveIntWithDefaultSchema(2),
|
|
OLLAMA_MAX_CONCURRENT_REQUESTS: positiveIntWithDefaultSchema(1),
|
|
|
|
MISTRAL_API_KEY: optionalStringSchema,
|
|
MISTRAL_MODEL: stringWithDefaultSchema("mistral-tiny-latest"),
|
|
MISTRAL_TRANSCRIPTION_MODEL: stringWithDefaultSchema("voxtral-mini-latest"),
|
|
MISTRAL_TTS_MODEL: stringWithDefaultSchema("voxtral-mini-tts-latest"),
|
|
MISTRAL_TTS_VOICE_ID: stringWithDefaultSchema("cb891218-482c-4392-9878-91e8d999d57a"),
|
|
MISTRAL_MAX_CONCURRENT_REQUESTS: positiveIntWithDefaultSchema(3),
|
|
|
|
OPENAI_BASE_URL: optionalStringSchema,
|
|
OPENAI_API_KEY: optionalStringSchema,
|
|
OPENAI_MODEL: stringWithDefaultSchema("gpt-4.1-nano"),
|
|
OPENAI_IMAGE_MODEL: stringWithDefaultSchema("gpt-image-1-mini"),
|
|
OPENAI_TRANSCRIPTION_MODEL: stringWithDefaultSchema("gpt-4o-mini-transcribe"),
|
|
OPENAI_TTS_MODEL: stringWithDefaultSchema("gpt-4o-mini-tts"),
|
|
OPENAI_TTS_VOICE: stringWithDefaultSchema("alloy"),
|
|
OPENAI_TTS_INSTRUCTIONS: optionalStringSchema,
|
|
OPENAI_MAX_CONCURRENT_REQUESTS: positiveIntWithDefaultSchema(3),
|
|
});
|
|
|
|
type StartupEnv = z.infer<typeof StartupEnvSchema>;
|
|
type RuntimeEnv = z.infer<typeof RuntimeEnvSchema>;
|
|
|
|
export class Environment {
|
|
private static readonly ENV_FILE_PATH = path.resolve(".env");
|
|
|
|
private static lastEnvMtimeMs: number | undefined;
|
|
private static lastSystemPromptMtimeMs: number | undefined;
|
|
private static lastRankerToolPromptMtimeMs: number | undefined;
|
|
private static envSystemPrompt: string | undefined;
|
|
private static envRankerToolPrompt: string | undefined;
|
|
|
|
static BOT_TOKEN: string = "";
|
|
static TEST_ENVIRONMENT: boolean = false;
|
|
static ADMIN_IDS: Set<number> = new Set<number>();
|
|
static MUTED_IDS: Set<number> = new Set<number>();
|
|
static CHAT_IDS_WHITELIST: Set<number> = new Set<number>();
|
|
static BOT_PREFIX: string = "";
|
|
static CREATOR_ID: number = 0;
|
|
static IS_DOCKER: boolean = false;
|
|
static DATA_PATH: string = "data";
|
|
static DB_FILE_NAME: string = "database.db";
|
|
static DB_PATH: string = "file:" + path.join(Environment.DATA_PATH, Environment.DB_FILE_NAME);
|
|
static DB_FILE_PATH?: string;
|
|
static DB_KIND: "sqlite" | "postgres" = "sqlite";
|
|
|
|
static ONLY_FOR_CREATOR_MODE: boolean = false;
|
|
|
|
static ENABLE_UNSAFE_EVAL: boolean = false;
|
|
|
|
static ANSWERS: Answers;
|
|
|
|
static MAX_PHOTO_SIZE: number = 0;
|
|
|
|
static PROCESS_LINKS: boolean = false;
|
|
static LOCALES_DIR: string = "locales";
|
|
|
|
static RATE_LIMIT_FALLBACK_POLICY: RateLimitFallbackPolicy = RateLimitFallbackPolicy.NOTIFY_USER;
|
|
static IMAGE_HANDLE_POLICY: ImageHandlePolicy = ImageHandlePolicy.HANDLE_IF_CAPABLE;
|
|
static IMAGE_HANDLE_FALLBACK_POLICY: ImageHandleFallbackPolicy = ImageHandleFallbackPolicy.NOTIFY_USER;
|
|
static TOOL_RANKER_FALLBACK_POLICY: ToolRankerFallbackPolicy = ToolRankerFallbackPolicy.ALL_TOOLS;
|
|
|
|
static BRAVE_SEARCH_API_KEY?: string;
|
|
static OPEN_WEATHER_MAP_API_KEY?: string;
|
|
|
|
static FILE_TOOLS_ROOT_DIR?: string;
|
|
static ENABLE_FS_TOOLS: boolean = false;
|
|
|
|
// AI Stuff
|
|
static DEFAULT_AI_PROVIDER: AiProvider = AiProvider.OLLAMA;
|
|
|
|
static SYSTEM_PROMPT?: string;
|
|
static RANKER_TOOL_PROMPT?: string;
|
|
static USE_NAMES_IN_PROMPT: boolean = false;
|
|
static USE_SYSTEM_PROMPT: boolean = true;
|
|
static SEND_TIME_TOOK: boolean = false;
|
|
|
|
static ENABLE_PYTHON_INTERPRETER: boolean = false;
|
|
|
|
static OLLAMA_API_KEY?: string;
|
|
static OLLAMA_ADDRESS?: string;
|
|
static OLLAMA_CHAT_MODEL: string = "";
|
|
static OLLAMA_IMAGE_MODEL: string = Environment.OLLAMA_CHAT_MODEL;
|
|
static OLLAMA_THINK_MODEL: string = Environment.OLLAMA_CHAT_MODEL;
|
|
static OLLAMA_AUDIO_MODEL: string = Environment.OLLAMA_CHAT_MODEL;
|
|
static OLLAMA_EMBEDDING_MODEL: string = "";
|
|
static OLLAMA_RAG_CHUNK_SIZE: number = 0;
|
|
static OLLAMA_RAG_CHUNK_OVERLAP: number = 0;
|
|
static OLLAMA_RAG_TOP_K: number = 0;
|
|
static OLLAMA_RAG_MAX_CONTEXT_CHARS: number = 0;
|
|
static OLLAMA_RAG_MIN_SCORE: number = 0.0;
|
|
static OLLAMA_RAG_MAX_ARCHIVE_FILES: number = 0;
|
|
static OLLAMA_RAG_MAX_ARCHIVE_BYTES: number = 0;
|
|
static OLLAMA_RAG_MAX_ARCHIVE_DEPTH: number = 0;
|
|
static OLLAMA_MAX_CONCURRENT_REQUESTS: number = 0;
|
|
|
|
static MISTRAL_API_KEY?: string;
|
|
static MISTRAL_MODEL: string = "";
|
|
static MISTRAL_TRANSCRIPTION_MODEL: string = "";
|
|
static MISTRAL_TTS_MODEL: string = "";
|
|
static MISTRAL_TTS_VOICE_ID: string = "";
|
|
static MISTRAL_MAX_CONCURRENT_REQUESTS: number = 0;
|
|
|
|
static OPENAI_BASE_URL?: string;
|
|
static OPENAI_API_KEY?: string;
|
|
static OPENAI_MODEL: string = "";
|
|
static OPENAI_IMAGE_MODEL: string = "";
|
|
static OPENAI_TRANSCRIPTION_MODEL: string = "";
|
|
static OPENAI_TTS_MODEL: string = "";
|
|
static OPENAI_TTS_VOICE: string = "";
|
|
static OPENAI_TTS_INSTRUCTIONS?: string;
|
|
static OPENAI_MAX_CONCURRENT_REQUESTS: number = 0;
|
|
|
|
static get databaseSummaryText(): string {
|
|
if (this.DB_KIND === "postgres") {
|
|
return "postgres";
|
|
}
|
|
|
|
if (this.DB_FILE_PATH) {
|
|
return `sqlite:${this.DB_FILE_PATH}`;
|
|
}
|
|
|
|
if (this.DB_PATH === ":memory:") {
|
|
return "sqlite:memory";
|
|
}
|
|
|
|
return this.DB_PATH.startsWith("file:") ? "sqlite:file" : "sqlite";
|
|
}
|
|
|
|
private static text(key: string, fallback: string, params: LocalizationParams = {}): string {
|
|
return Localization.text(key, params, fallback);
|
|
}
|
|
|
|
private static textArray(key: string, fallback: string[], params: LocalizationParams = {}): string[] {
|
|
return Localization.textArray(key, params, fallback);
|
|
}
|
|
|
|
static get errorText() {
|
|
return this.text("errorText", "⚠️ An error occurred.");
|
|
}
|
|
|
|
static get waitThinkText() {
|
|
return this.text("waitThinkText", "⏳ Let me think...");
|
|
}
|
|
|
|
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 regenerateText() {
|
|
return this.text("regenerateText", "🔄 Regenerate");
|
|
}
|
|
|
|
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 noAccessText() {
|
|
return this.text("noAccessText", "No access");
|
|
}
|
|
|
|
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 notChatAdministratorText() {
|
|
return this.text("notChatAdministratorText", "You are not a chat administrator.");
|
|
}
|
|
|
|
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 dockerContainerLabelText() {
|
|
return this.text("dockerContainerLabelText", "Docker container");
|
|
}
|
|
|
|
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 systemInfoRuntimeLabelText() {
|
|
return this.text("systemInfoRuntimeLabelText", "RUNTIME");
|
|
}
|
|
|
|
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 systemInfoCpuCoresText() {
|
|
return this.text("systemInfoCpuCoresText", "cores");
|
|
}
|
|
|
|
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 idReplyLabelText() {
|
|
return this.text("idReplyLabelText", "reply id");
|
|
}
|
|
|
|
static get runtimeProviderLabelText() {
|
|
return this.text("runtimeProviderLabelText", "provider");
|
|
}
|
|
|
|
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 databaseImportDoneText() {
|
|
return this.text("databaseImportDoneText", "Database imported successfully.");
|
|
}
|
|
|
|
static get databaseImportNeedJsonText() {
|
|
return this.text("databaseImportNeedJsonText", "Send a JSON backup file or pass JSON after /importdb.");
|
|
}
|
|
|
|
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 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 pipelineFallbackGenericText() {
|
|
return this.text("pipelineFallbackGenericText", "⚠️ I had to skip part of the request, but I can continue.");
|
|
}
|
|
|
|
static get pipelineFallbackNotifyText() {
|
|
return this.text("pipelineFallbackNotifyText", "⚠️ I hit a problem and need to continue with a fallback.");
|
|
}
|
|
|
|
static get pipelineFallbackFailText() {
|
|
return this.text("pipelineFallbackFailText", "⚠️ I could not finish this request.");
|
|
}
|
|
|
|
static get pipelineFallbackRagText() {
|
|
return this.text("pipelineFallbackRagText", "⚠️ Document retrieval failed, so I will answer without RAG.");
|
|
}
|
|
|
|
static get pipelineFallbackSpeechToTextText() {
|
|
return this.text("pipelineFallbackSpeechToTextText", "⚠️ Speech transcription failed, so I will continue without the audio transcript.");
|
|
}
|
|
|
|
static get pipelineFallbackTextToSpeechText() {
|
|
return this.text("pipelineFallbackTextToSpeechText", "⚠️ Text-to-speech failed, so I will continue without audio output.");
|
|
}
|
|
|
|
static get pipelineFallbackToolText() {
|
|
return this.text("pipelineFallbackToolText", "⚠️ Tool execution failed, so I will continue without that tool.");
|
|
}
|
|
|
|
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 userSettingsImageOutputSelectionTitle() {
|
|
return this.text("userSettingsImageOutputSelectionTitle", "Image Output 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 userSettingsImageOutputLabel() {
|
|
return this.text("userSettingsImageOutputLabel", "Image output");
|
|
}
|
|
|
|
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 userSettingsImageOutputButtonPrefix() {
|
|
return this.text("userSettingsImageOutputButtonPrefix", "Image output");
|
|
}
|
|
|
|
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 get userSettingsImageOutputPhotoText() {
|
|
return this.text("userSettingsImageOutputPhotoText", "As photo");
|
|
}
|
|
|
|
static get userSettingsImageOutputDocumentText() {
|
|
return this.text("userSettingsImageOutputDocumentText", "As document");
|
|
}
|
|
|
|
static commandTitles = {
|
|
ae: "/ae",
|
|
adminsAdd: "/addAdmin",
|
|
adminsRemove: "/removeAdmin",
|
|
ban: "/ban [reply]",
|
|
choice: "/choice a, b, ..., c",
|
|
coin: "/coin",
|
|
debug: "/debug",
|
|
dice: "/dice",
|
|
distort: "/distort [amp] [wavelength]",
|
|
help: "/help",
|
|
id: "/id",
|
|
ignore: "/ignore",
|
|
info: "/info",
|
|
leave: "/leave",
|
|
mistralChat: "/mistral",
|
|
mistralGetModel: "/MistralGetModel",
|
|
mistralListModels: "/MistralListModels",
|
|
mistralSetModel: "/MistralSetModel",
|
|
ollamaChat: "/ollama",
|
|
ollamaGetModel: "/OllamaGetModel",
|
|
ollamaListModels: "/OllamaListModels",
|
|
ollamaSearch: "/search",
|
|
ollamaSetModel: "/OllamaSetModel",
|
|
openAiChat: "/openAI",
|
|
openAiGetModel: "/OpenAIGetModel",
|
|
openAiListModels: "/OpenAIListModels",
|
|
openAiSetModel: "/OpenAISetModel",
|
|
ping: "/ping",
|
|
qr: "/qr",
|
|
quote: "/quote",
|
|
randomInt: "/randomInt",
|
|
randomString: "/randomString",
|
|
settings: "/settings",
|
|
shutdown: "/shutdown",
|
|
speechToText: "/stt",
|
|
start: "/start",
|
|
systemInfo: "/systemInfo",
|
|
textToSpeech: "/tts",
|
|
title: "/title",
|
|
test: "test",
|
|
transliteration: "/tr [text or reply]",
|
|
unban: "/unban [reply]",
|
|
unignore: "/unignore",
|
|
uptime: "/uptime",
|
|
whatBetter: "/what better [a] or [b]",
|
|
when: "/when [value]",
|
|
} as const;
|
|
|
|
static get commandDescriptions() {
|
|
return {
|
|
ae: this.text("commandDescriptions.ae", "evaluation"),
|
|
adminsAdd: this.text("commandDescriptions.adminsAdd", "Add user to admins"),
|
|
adminsRemove: this.text("commandDescriptions.adminsRemove", "Remove user from admins"),
|
|
ban: this.text("commandDescriptions.ban", "ban user from chat"),
|
|
choice: this.text("commandDescriptions.choice", "Choose a random value"),
|
|
coin: this.text("commandDescriptions.coin", "Heads or tails"),
|
|
debug: this.text("commandDescriptions.debug", "Returns msg (or reply) as json"),
|
|
dice: this.text("commandDescriptions.dice", "Sends random or specific dice"),
|
|
distort: this.text("commandDescriptions.distort", "Distortion of picture"),
|
|
help: this.text("commandDescriptions.help", "Show list of commands"),
|
|
id: this.text("commandDescriptions.id", "ID of chat, user and reply (if replied to any message)"),
|
|
ignore: this.text("commandDescriptions.ignore", "Bot will ignore user"),
|
|
info: this.text("commandDescriptions.info", "Info about bot"),
|
|
leave: this.text("commandDescriptions.leave", "Bot will leave current chat"),
|
|
mistralChat: this.text("commandDescriptions.mistralChat", "Chat with AI (Mistral)"),
|
|
mistralGetModel: this.text("commandDescriptions.mistralGetModel", "Get current Mistral model"),
|
|
mistralListModels: this.text("commandDescriptions.mistralListModels", "List all Mistral models"),
|
|
mistralSetModel: this.text("commandDescriptions.mistralSetModel", "Set Mistral model"),
|
|
ollamaChat: this.text("commandDescriptions.ollamaChat", "Chat with AI (Ollama)"),
|
|
ollamaGetModel: this.text("commandDescriptions.ollamaGetModel", "Get current Ollama model"),
|
|
ollamaListModels: this.text("commandDescriptions.ollamaListModels", "List all Ollama models"),
|
|
ollamaSearch: this.text("commandDescriptions.ollamaSearch", "Web search via Ollama"),
|
|
ollamaSetModel: this.text("commandDescriptions.ollamaSetModel", "Set Ollama model"),
|
|
openAiChat: this.text("commandDescriptions.openAiChat", "Chat with AI (OpenAI)"),
|
|
openAiGetModel: this.text("commandDescriptions.openAiGetModel", "Get current OpenAI model"),
|
|
openAiListModels: this.text("commandDescriptions.openAiListModels", "List all OpenAI models"),
|
|
openAiSetModel: this.text("commandDescriptions.openAiSetModel", "Set OpenAI model"),
|
|
ping: this.text("commandDescriptions.ping", "Ping between received and sent message"),
|
|
qr: this.text("commandDescriptions.qr", "Generates QR-code from text you sent or replied to."),
|
|
quote: this.text("commandDescriptions.quote", "Make quote from text (or quote)"),
|
|
randomInt: this.text("commandDescriptions.randomInt", "Ranged random integer from parameters"),
|
|
randomString: this.text("commandDescriptions.randomString", "literally random string (up to 4096 symbols)"),
|
|
settings: this.text("commandDescriptions.settings", "User settings"),
|
|
shutdown: this.text("commandDescriptions.shutdown", "Self-destruction sequence for bot (shutdown)"),
|
|
speechToText: this.text("commandDescriptions.speechToText", "Transcribe speech to text"),
|
|
start: this.text("commandDescriptions.start", "Start the bot"),
|
|
systemInfo: this.text("commandDescriptions.systemInfo", "System information"),
|
|
textToSpeech: this.text("commandDescriptions.textToSpeech", "Generate speech from text"),
|
|
title: this.text("commandDescriptions.title", "Change group title"),
|
|
test: this.text("commandDescriptions.test", "System functionality check"),
|
|
transliteration: this.text("commandDescriptions.transliteration", "Transliteration EN <--> RU"),
|
|
unban: this.text("commandDescriptions.unban", "unban user from chat"),
|
|
unignore: this.text("commandDescriptions.unignore", "Bot will start responding to the user"),
|
|
uptime: this.text("commandDescriptions.uptime", "Bot's uptime"),
|
|
whatBetter: this.text("commandDescriptions.whatBetter", "either a or b randomly (50% chance)"),
|
|
when: this.text("commandDescriptions.when", "random date"),
|
|
} as const;
|
|
}
|
|
|
|
static getUserSettingsTitle(screen: string): string {
|
|
if (screen === "provider") return this.userSettingsAiProviderSelectionTitle;
|
|
if (screen === "interfaceLanguage") return this.userSettingsInterfaceLanguageSelectionTitle;
|
|
if (screen === "responseLanguage" || screen === "language") return this.userSettingsResponseLanguageSelectionTitle;
|
|
if (screen === "contextSize") return this.userSettingsContextSizeSelectionTitle;
|
|
if (screen === "voiceMode") return this.userSettingsVoiceModeSelectionTitle;
|
|
if (screen === "imageOutput") return this.userSettingsImageOutputSelectionTitle;
|
|
return this.userSettingsTitle;
|
|
}
|
|
|
|
static getUserSettingsFieldText(label: string, value: string): string {
|
|
return `${label}: ${value}`;
|
|
}
|
|
|
|
static getUserSettingsSelectedText(text: string): string {
|
|
return `${this.userSettingsSelectedPrefix}${text}`;
|
|
}
|
|
|
|
static getUserSettingsContextSizeText(size: number): string {
|
|
return this.text("getUserSettingsContextSizeText", "{size} tokens", {size});
|
|
}
|
|
|
|
static getCancelledText(provider: string): string {
|
|
return this.text("getCancelledText", "{provider}\n❌ Generation cancelled.", {provider});
|
|
}
|
|
|
|
static get startingImageGenText() {
|
|
return this.text("startingImageGenText", "🌈 Starting 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 {
|
|
return this.text("getPartialImageGenText", "🌈 Generating image ({iteration}/{total})...", {iteration, total});
|
|
}
|
|
|
|
static getImageGenDoneText(model?: string): string {
|
|
return model
|
|
? this.text("getImageGenDoneText.withModel", "👨🎨 Image generated. Model: `{model}`.", {model})
|
|
: this.text("getImageGenDoneText.default", "👨🎨 Image generated.");
|
|
}
|
|
|
|
static getErrorText(error?: ErrorLike | BoundaryValue | null | undefined): string {
|
|
if (!error) return this.errorText;
|
|
|
|
const reason = error instanceof Error ? error.message : String(error);
|
|
return this.text("getErrorText.withReason", "{errorText} Reason:\n{reason}", {
|
|
errorText: this.errorText,
|
|
reason,
|
|
});
|
|
}
|
|
|
|
static getUptimeText(processUptime: string, osUptime: string): string {
|
|
return `${Environment.IS_DOCKER ? this.dockerContainerLabelText : this.processLabelText}:\n${processUptime}\n\n${this.systemLabelText}:\n${osUptime}`;
|
|
}
|
|
|
|
static getExpandableBlockquoteText(content: string): string {
|
|
return `<blockquote expandable>${content}</blockquote>`;
|
|
}
|
|
|
|
static getSystemSpecsText(params: {
|
|
os: string;
|
|
runtime: string;
|
|
docker: boolean;
|
|
cpu: string;
|
|
ramGb: string;
|
|
}): string {
|
|
return [
|
|
`${this.systemInfoOsLabelText}: ${params.os}`,
|
|
`${this.systemInfoRuntimeLabelText}: ${params.runtime}`,
|
|
`${this.systemInfoDockerLabelText}: ${params.docker}`,
|
|
`${this.systemInfoCpuLabelText}: ${params.cpu}`,
|
|
`${this.systemInfoRamLabelText}: ${params.ramGb} GB`,
|
|
].join("\n");
|
|
}
|
|
|
|
static getIdText(chatId: number | string, fromId: number | string | undefined, replyId?: number | string): string {
|
|
let text = `${this.idChatLabelText}: \n\`\`\`${chatId}\`\`\` \n${this.idFromLabelText}: \n\`\`\`${fromId}\`\`\``;
|
|
if (replyId !== undefined) {
|
|
text += ` \n${this.idReplyLabelText}: \n\`\`\`${replyId}\`\`\``;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
static getRandomIntRangeText(min: number, max: number, value: number): string {
|
|
return this.text("getRandomIntRangeText", "[{min}; {max}]: {value}", {min, max, value});
|
|
}
|
|
|
|
static getRuntimeCapabilityLineText(params: {
|
|
state: string;
|
|
title: string;
|
|
model?: string;
|
|
endpointBaseUrl?: string;
|
|
external?: boolean;
|
|
}): string {
|
|
const modelPart = params.model ? ` (${params.model})` : "";
|
|
const endpointPart = params.endpointBaseUrl ? ` @ ${params.endpointBaseUrl}` : "";
|
|
const externalPart = params.external ? ` ${this.runtimeExternalLabelText}` : "";
|
|
return this.text("getRuntimeCapabilityLineText", "{state} {title}{modelPart}{endpointPart}{externalPart}", {
|
|
state: params.state,
|
|
title: params.title,
|
|
modelPart,
|
|
endpointPart,
|
|
externalPart,
|
|
});
|
|
}
|
|
|
|
static getRuntimeModelInfoText(provider: string, model: string | undefined, capabilities: string[]): string {
|
|
return [
|
|
`${this.runtimeProviderLabelText}: ${provider}`,
|
|
`${this.runtimeModelLabelText}: ${model}`,
|
|
"",
|
|
`${this.runtimeCapabilitiesLabelText}:`,
|
|
...capabilities,
|
|
].join("\n");
|
|
}
|
|
|
|
static getInfoToolsBlockText(toolNames: string[]): string {
|
|
return [
|
|
`\`\`\`${this.infoToolsBlockLabelText}`,
|
|
toolNames.map(name => `- ${name}`).join("\n"),
|
|
"```",
|
|
].join("\n");
|
|
}
|
|
|
|
static getInfoCommandsBlockText(params: {
|
|
publicCommands: number;
|
|
privateCommands: number;
|
|
chatCommands: number;
|
|
callbackCommands: number;
|
|
}): string {
|
|
return [
|
|
`\`\`\`${this.infoCommandsBlockLabelText}`,
|
|
`${this.infoPublicLabelText}: ${params.publicCommands}`,
|
|
`${this.infoPrivateLabelText}: ${params.privateCommands}`,
|
|
`${this.infoChatLabelText}: ${params.chatCommands}`,
|
|
`${this.infoCallbackLabelText}: ${params.callbackCommands}`,
|
|
"```",
|
|
].join("\n");
|
|
}
|
|
|
|
static getUseToolText(toolCalls: ToolCallData[] | string[]): string {
|
|
const isString = (toolCall: ToolCallData | string) => {
|
|
return typeof toolCall === "string";
|
|
};
|
|
|
|
return toolCalls.map(toolCall => {
|
|
const name = isString(toolCall) ? toolCall : toolCall.name;
|
|
return name === PYTHON_INTERPRETER_TOOL_NAME
|
|
? this.text("getUseToolText.python", "👨💻 Running `Python`")
|
|
: name === "code_interpreter"
|
|
? this.text("getUseToolText.codeInterpreter", "👨💻 Running `Code Interpreter`")
|
|
: this.text("getUseToolText.default", "🔧 Using tool `{name}`", {name});
|
|
}).join("\n");
|
|
}
|
|
|
|
static getAnalyzingDocumentText(documentNames?: string[]): string {
|
|
if (!documentNames) return this.text("getAnalyzingDocumentText.default", "🔍 Analyzing the document...");
|
|
if (documentNames.length === 1) {
|
|
return this.text("getAnalyzingDocumentText.single", "🔍 Analyzing document: `{name}`", {name: documentNames[0]});
|
|
}
|
|
|
|
return this.text("getAnalyzingDocumentText.many", "🔍 Analyzing documents: {names}", {
|
|
names: documentNames.map(n => `\`${n}\``).join(", "),
|
|
});
|
|
}
|
|
|
|
static getPreparingRAGText(documentNames?: string[]): string {
|
|
if (!documentNames) return this.text("getPreparingRAGText.default", "🔍 Preparing RAG for the document...");
|
|
if (documentNames.length === 1) {
|
|
return this.text("getPreparingRAGText.single", "🔍 Preparing RAG for document: `{name}`", {name: documentNames[0]});
|
|
}
|
|
|
|
return this.text("getPreparingRAGText.many", "🔍 Preparing RAG for documents: {names}", {
|
|
names: documentNames.map(n => `\`${n}\``).join(", "),
|
|
});
|
|
}
|
|
|
|
static getSelectingToolsText(): string {
|
|
return this.text("getSelectingToolsText", "🧩 Выбираю подходящие инструменты...");
|
|
}
|
|
|
|
static getBuildingRAGIndexText(modelName?: string): string {
|
|
return modelName
|
|
? this.text("getBuildingRAGIndexText.withModel", "🧠 Building RAG index: `{modelName}`.", {modelName})
|
|
: this.text("getBuildingRAGIndexText.default", "🧠 Building RAG index...");
|
|
}
|
|
|
|
static getAiQueueText(provider: AiProvider, requestsBefore: number): string {
|
|
const count = Math.max(0, requestsBefore);
|
|
const beforeText = count === 0 ? this.text("queueNoneText", "none") : count.toString();
|
|
return [
|
|
this.text("getAiQueueText.queued", "⏳ Request to {provider} is queued.", {provider: provider.toString().toLowerCase()}),
|
|
this.text("getAiQueueText.ahead", "Requests ahead: {count}.", {count: beforeText}),
|
|
].join("\n");
|
|
}
|
|
|
|
static getTelegramFileTooLargeText(fileName: string, maxSizeMb: number): string {
|
|
return this.text("getTelegramFileTooLargeText", "File {fileName} is larger than {maxSizeMb} MB and cannot be sent.", {
|
|
fileName,
|
|
maxSizeMb
|
|
});
|
|
}
|
|
|
|
static getUserIsNowAdminText(name: string): string {
|
|
return this.text("getUserIsNowAdminText", "{name} is now an admin!", {name});
|
|
}
|
|
|
|
static getUserAlreadyAdminText(name: string): string {
|
|
return this.text("getUserAlreadyAdminText", "{name} is already an admin 🤔", {name});
|
|
}
|
|
|
|
static getUserNoLongerAdminText(name: string): string {
|
|
return this.text("getUserNoLongerAdminText", "{name} is no longer an admin!", {name});
|
|
}
|
|
|
|
static getUserWasNotAdminText(name: string): string {
|
|
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 botCreatorAlreadyAdminText() {
|
|
return this.text("botCreatorAlreadyAdminText", "The bot creator is already 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 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 {
|
|
return this.text("getUserBannedText", "{name} banned 🚫", {name});
|
|
}
|
|
|
|
static getUserBanFailedText(name: string): string {
|
|
return this.text("getUserBanFailedText", "Could not ban {name} ☹️", {name});
|
|
}
|
|
|
|
static getUserUnbannedText(name: string): string {
|
|
return this.text("getUserUnbannedText", "{name} unbanned ⛓️💥", {name});
|
|
}
|
|
|
|
static getUserUnbanFailedText(name: string): string {
|
|
return this.text("getUserUnbanFailedText", "Could not unban {name} ☹️", {name});
|
|
}
|
|
|
|
static getUserIgnoredText(name: string): string {
|
|
return this.text("getUserIgnoredText", "{name} is muted! 🔇", {name});
|
|
}
|
|
|
|
static getUserAlreadyIgnoredText(name: string): string {
|
|
return this.text("getUserAlreadyIgnoredText", "{name} is already muted 🤔", {name});
|
|
}
|
|
|
|
static getUserIgnoreFailedText(name: string): string {
|
|
return this.text("getUserIgnoreFailedText", "Could not mute {name} ☹️", {name});
|
|
}
|
|
|
|
static getUserUnignoredText(name: string): string {
|
|
return this.text("getUserUnignoredText", "{name} is no longer muted! 🔈", {name});
|
|
}
|
|
|
|
static getUserWasNotIgnoredText(name: string): string {
|
|
return this.text("getUserWasNotIgnoredText", "{name} was not muted 🤔", {name});
|
|
}
|
|
|
|
static getUserUnignoreFailedText(name: string): string {
|
|
return this.text("getUserUnignoreFailedText", "Could not unmute {name} ☹️", {name});
|
|
}
|
|
|
|
static getChoiceText(choice: string): string {
|
|
return this.text("getChoiceText", "Chose *{choice}*", {choice});
|
|
}
|
|
|
|
static getCoinResultText(result: string): string {
|
|
return this.text("getCoinResultText", "It landed on *{result}*", {result});
|
|
}
|
|
|
|
static get coinHeadsText() {
|
|
return this.text("coinHeadsText", "Heads");
|
|
}
|
|
|
|
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 {
|
|
return this.text("getDistortionReadyCaption", "Distortion ready ✅ (amp={amp}, wavelength={wavelength})", {
|
|
amp,
|
|
wavelength
|
|
});
|
|
}
|
|
|
|
static getDistortFailedText(error: ErrorLike | BoundaryValue | null | undefined): string {
|
|
return this.text("getDistortFailedText", "Could not distort image: {reason}", {
|
|
reason: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
|
|
static getLoadedModelsText(modelNames: string[]): string {
|
|
return this.text("getLoadedModelsText", "Loaded models: {models}", {models: modelNames.join(", ")});
|
|
}
|
|
|
|
static getSelectedModelText(model: string): string {
|
|
return this.text("getSelectedModelText", "Selected model: `{model}`", {model});
|
|
}
|
|
|
|
static getSelectedModelWithInfoText(model: string, info: string): string {
|
|
return this.text("getSelectedModelWithInfoText", "Selected model \"{model}\"\n\n{info}", {model, info});
|
|
}
|
|
|
|
static getModelIsNotSetCurrentText(model: string): string {
|
|
return this.text("getModelIsNotSetCurrentText", "Model is not set. Current model: \"{model}\"", {model});
|
|
}
|
|
|
|
static getCurrentModelText(model: string): string {
|
|
return this.text("getCurrentModelText", "Current model: `{model}`", {model});
|
|
}
|
|
|
|
static getLoadingModelText(model: string): string {
|
|
return this.text("getLoadingModelText", "Loading model `{model}`...", {model});
|
|
}
|
|
|
|
static getCurrentModelUnsupportedInputText(model: string, providerName: string, inputKind: string): string {
|
|
return this.text("getCurrentModelUnsupportedInputText", "⚠️ Current model `{model}` ({providerName}) does not support {inputKind}.", {
|
|
model,
|
|
providerName,
|
|
inputKind,
|
|
});
|
|
}
|
|
|
|
static getDocumentIsEmptyText(fileName: string): string {
|
|
return this.text("getDocumentIsEmptyText", "Document {fileName} is empty or contains no readable text.", {fileName});
|
|
}
|
|
|
|
static getDocumentContentText(fileName: string, content: string): string {
|
|
return this.text("getDocumentContentText", "{label} for \"{fileName}\":\n\n{content}", {
|
|
label: this.documentContentLabelText,
|
|
fileName,
|
|
content,
|
|
});
|
|
}
|
|
|
|
static getMistralUploadedDocumentIdMissingText(fileName: string): string {
|
|
return this.text("getMistralUploadedDocumentIdMissingText", "Mistral did not return an uploaded document id for {fileName}.", {fileName});
|
|
}
|
|
|
|
static getMistralDocumentProcessingFailedText(fileName: string, status: string): string {
|
|
return this.text("getMistralDocumentProcessingFailedText", "Mistral could not process document {fileName}: {status}", {
|
|
fileName,
|
|
status
|
|
});
|
|
}
|
|
|
|
static getMistralDocumentProcessingTimedOutText(fileName: string): string {
|
|
return this.text("getMistralDocumentProcessingTimedOutText", "Mistral did not process document {fileName} within the allotted time.", {fileName});
|
|
}
|
|
|
|
static getAttachmentMissingFromCacheText(fileName: string): string {
|
|
return this.text("getAttachmentMissingFromCacheText", "⚠️ Attachment file is missing from the cache: {fileName}", {fileName});
|
|
}
|
|
|
|
static getZipInvalidLocalHeaderText(entryName: string): string {
|
|
return this.text("getZipInvalidLocalHeaderText", "ZIP archive is corrupted: invalid local header for {entryName}.", {entryName});
|
|
}
|
|
|
|
static getZipUnsupportedCompressionMethodText(method: number, entryName: string): string {
|
|
return this.text("getZipUnsupportedCompressionMethodText", "ZIP archive uses unsupported compression method {method} for {entryName}.", {
|
|
method,
|
|
entryName
|
|
});
|
|
}
|
|
|
|
static getGzipUncompressedLimitText(maxBytes: number): string {
|
|
return this.text("getGzipUncompressedLimitText", "GZIP archive exceeds the uncompressed data limit ({maxBytes} bytes).", {maxBytes});
|
|
}
|
|
|
|
static getNestedArchiveDepthLimitText(maxDepth: number): string {
|
|
return this.text("getNestedArchiveDepthLimitText", "nested archive depth limit reached ({maxDepth})", {maxDepth});
|
|
}
|
|
|
|
static getUnsupportedArchiveFormatText(fileName: string): string {
|
|
return this.text("getUnsupportedArchiveFormatText", "Archive format \"{fileName}\" is not supported by local RAG.", {fileName});
|
|
}
|
|
|
|
static getDocumentEmptyOrNoExtractableText(fileName: string): string {
|
|
return this.text("getDocumentEmptyOrNoExtractableText", "Document \"{fileName}\" is empty or contains no extractable text.", {fileName});
|
|
}
|
|
|
|
static getUnsupportedLocalRagDocumentFormatText(fileName: string): string {
|
|
return this.text("getUnsupportedLocalRagDocumentFormatText", "Document format \"{fileName}\" is not supported by local RAG. Supported formats: text files, code, CSV, JSON, Markdown, YAML, XML, DOCX, text PDFs, and ZIP/TAR/GZIP archives containing those files.", {fileName});
|
|
}
|
|
|
|
static getOllamaEmbeddingInvalidResponseText(model: string): string {
|
|
return this.text("getOllamaEmbeddingInvalidResponseText", "Ollama embedding model \"{model}\" returned an invalid response.", {model});
|
|
}
|
|
|
|
static getProviderNotAvailableForAccessText(providerName: string): string {
|
|
return this.text("getProviderNotAvailableForAccessText", "Provider {providerName} is not available for your access level.", {providerName});
|
|
}
|
|
|
|
static getProviderSpeechToTextUnsupportedText(providerName: string): string {
|
|
return this.text("getProviderSpeechToTextUnsupportedText", "Provider {providerName} does not support speech-to-text or is not configured for it.", {providerName});
|
|
}
|
|
|
|
static getProviderTextToSpeechUnsupportedText(providerName: string): string {
|
|
return this.text("getProviderTextToSpeechUnsupportedText", "Provider {providerName} does not support text-to-speech or is not configured for it.", {providerName});
|
|
}
|
|
|
|
static getTextToSpeechTooLongText(actualLength: number, maxLength: number): string {
|
|
return this.text("getTextToSpeechTooLongText", "Text for speech synthesis is too long: {actualLength} characters, maximum {maxLength}.", {
|
|
actualLength,
|
|
maxLength,
|
|
});
|
|
}
|
|
|
|
static getTextToSpeechCaption(providerName: string, model: string, voice?: string): string {
|
|
return [
|
|
`TTS: ${providerName}`,
|
|
`model: ${model}`,
|
|
voice ? `voice: ${voice}` : null,
|
|
].filter(Boolean).join("\n");
|
|
}
|
|
|
|
static getQrCodeTextTooLongText(actualLength: number, maxLength: number): string {
|
|
return this.text("getQrCodeTextTooLongText", "Text is too long for QR ({actualLength} characters). It will be trimmed to {maxLength} characters.", {
|
|
actualLength,
|
|
maxLength,
|
|
});
|
|
}
|
|
|
|
static getQrCodeReadyText(content: string): string {
|
|
return this.text("getQrCodeReadyText", "QR code ready ✅\nContent:\n<blockquote expandable>{content}</blockquote>", {content});
|
|
}
|
|
|
|
static getQrCodeFailedText(error: ErrorLike | BoundaryValue | null | undefined): string {
|
|
return this.text("getQrCodeFailedText", "Could not generate QR: {reason}", {
|
|
reason: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
|
|
static get shutdownFallbackText() {
|
|
return this.text("shutdownFallbackText", "...");
|
|
}
|
|
|
|
static get shutdownSequenceTexts() {
|
|
return this.textArray("shutdownSequenceTexts", [
|
|
"well then, everyone",
|
|
"it was nice talking to you",
|
|
"but it is time for me to rest",
|
|
"all the best",
|
|
]);
|
|
}
|
|
|
|
static get shutdownDoneText() {
|
|
return this.text("shutdownDoneText", "*R.I.P*");
|
|
}
|
|
|
|
static getWhenPrefixText(): string {
|
|
return this.text("getWhenPrefixText", "in ");
|
|
}
|
|
|
|
static get whenNowText() {
|
|
return this.text("whenNowText", "right now");
|
|
}
|
|
|
|
static get whenNeverText() {
|
|
return this.text("whenNeverText", "never");
|
|
}
|
|
|
|
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 {
|
|
const pluralUnit = value === 1 ? unit : this.text("getWhenPluralUnitText", "{unit}s", {unit});
|
|
return this.text("getWhenDurationText", "{prefix}{value} {unit}", {
|
|
prefix: this.getWhenPrefixText(),
|
|
value,
|
|
unit: pluralUnit,
|
|
});
|
|
}
|
|
|
|
static getPingReportText(
|
|
telegramPingMs: string,
|
|
apiPingMs: string,
|
|
messageDate: string,
|
|
messageTime: string,
|
|
localDate: string,
|
|
localTime: string,
|
|
): string {
|
|
return this.text("getPingReportText", "```ping\nTG: {telegramPingMs}ms\nAPI {apiPingMs}ms\n\n🗓️ Message date: {messageDate}\n🕒 Message time: {messageTime}\n\n🗓️ Local date : {localDate}\n🕒 Local time: {localTime}```", {
|
|
telegramPingMs,
|
|
apiPingMs,
|
|
messageDate,
|
|
messageTime,
|
|
localDate,
|
|
localTime,
|
|
});
|
|
}
|
|
|
|
static getAiProviderMaxConcurrentRequests(provider: AiProvider): number {
|
|
switch (provider) {
|
|
case AiProvider.OLLAMA:
|
|
return Environment.OLLAMA_MAX_CONCURRENT_REQUESTS;
|
|
case AiProvider.MISTRAL:
|
|
return Environment.MISTRAL_MAX_CONCURRENT_REQUESTS;
|
|
case AiProvider.OPENAI:
|
|
return Environment.OPENAI_MAX_CONCURRENT_REQUESTS;
|
|
}
|
|
}
|
|
|
|
private static processEnvAsRecord(): EnvRecord {
|
|
return Object.fromEntries(
|
|
Object.entries(process.env)
|
|
.filter((entry): entry is [string, string] => typeof entry[1] === "string"),
|
|
);
|
|
}
|
|
|
|
private static parseNumberSet(value: string | undefined): Set<number> {
|
|
if (!value) {
|
|
return new Set<number>();
|
|
}
|
|
|
|
const numbers = value
|
|
.split(",")
|
|
.map(e => Number.parseInt(e.trim(), 10))
|
|
.filter(Number.isSafeInteger);
|
|
|
|
return new Set<number>(numbers);
|
|
}
|
|
|
|
private static getFileMtimeMs(filePath: string): number | undefined {
|
|
try {
|
|
return fs.statSync(filePath).mtimeMs;
|
|
} catch (e) {
|
|
if (e instanceof Error && "code" in e && (e as {code?: string}).code === "ENOENT") {
|
|
return undefined;
|
|
}
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private static readEnvFile(): EnvRecord {
|
|
if (!fs.existsSync(Environment.ENV_FILE_PATH)) {
|
|
return {};
|
|
}
|
|
|
|
const envFile = fs.readFileSync(Environment.ENV_FILE_PATH, "utf8");
|
|
return parseDotEnv(envFile);
|
|
}
|
|
|
|
private static readConfigSource(): EnvRecord {
|
|
return {
|
|
...Environment.readEnvFile(),
|
|
...Environment.processEnvAsRecord(),
|
|
};
|
|
}
|
|
|
|
static getOptionalConfigValue(name: string): string | undefined {
|
|
return normalizeString(Environment.readConfigSource()[name]);
|
|
}
|
|
|
|
private static getSystemPromptPath(): string {
|
|
return path.join(Environment.DATA_PATH, "SYSTEM_PROMPT.md");
|
|
}
|
|
|
|
private static getRankerToolPromptPath(): string {
|
|
return path.join(Environment.DATA_PATH, "TOOL_RANKER_PROMPT.md");
|
|
}
|
|
|
|
private static readSystemPrompt(): string | undefined {
|
|
const promptPath = Environment.getSystemPromptPath();
|
|
|
|
if (!fs.existsSync(promptPath)) {
|
|
return undefined;
|
|
}
|
|
|
|
const prompt = fs.readFileSync(promptPath, "utf8").trim();
|
|
return prompt.length > 0 ? prompt : undefined;
|
|
}
|
|
|
|
private static readRankerToolPromptPath(): string | undefined {
|
|
const promptPath = Environment.getRankerToolPromptPath();
|
|
|
|
if (!fs.existsSync(promptPath)) {
|
|
return undefined;
|
|
}
|
|
|
|
const prompt = fs.readFileSync(promptPath, "utf8").trim();
|
|
return prompt.length > 0 ? prompt : undefined;
|
|
}
|
|
|
|
private static refreshSystemPrompt(): void {
|
|
Environment.SYSTEM_PROMPT = Environment.readSystemPrompt() ?? Environment.envSystemPrompt;
|
|
}
|
|
|
|
private static refreshRankerToolPrompt(): void {
|
|
Environment.RANKER_TOOL_PROMPT = Environment.readRankerToolPromptPath() ?? Environment.envRankerToolPrompt;
|
|
}
|
|
|
|
private static applyStartupEnv(env: StartupEnv): void {
|
|
Environment.BOT_TOKEN = env.BOT_TOKEN;
|
|
Environment.TEST_ENVIRONMENT = env.TEST_ENVIRONMENT;
|
|
Environment.IS_DOCKER = env.IS_DOCKER ?? false;
|
|
|
|
const defaultDataPath = env.DATA_PATH
|
|
?? (Environment.IS_DOCKER
|
|
? "/" + path.join("config", "data")
|
|
: path.join(os.homedir(), ".local", "share", "tg-chat-bot"));
|
|
const defaultDatabaseUrl = "file:" + path.join(defaultDataPath, Environment.DB_FILE_NAME);
|
|
const databaseUrl = env.DATABASE_URL ?? env.DB_PATH ?? defaultDatabaseUrl;
|
|
|
|
Environment.DATA_PATH = defaultDataPath;
|
|
Environment.DB_PATH = databaseUrl;
|
|
Environment.DB_KIND = /^postgres(?:ql)?:\/\//i.test(databaseUrl) ? "postgres" : "sqlite";
|
|
Environment.DB_FILE_PATH = databaseUrl.startsWith("file:")
|
|
? databaseUrl.slice("file:".length)
|
|
: undefined;
|
|
}
|
|
|
|
private static applyRuntimeEnv(env: RuntimeEnv): void {
|
|
Environment.CHAT_IDS_WHITELIST = Environment.parseNumberSet(env.CHAT_IDS_WHITELIST);
|
|
Environment.BOT_PREFIX = env.BOT_PREFIX;
|
|
Environment.CREATOR_ID = env.CREATOR_ID;
|
|
Environment.ONLY_FOR_CREATOR_MODE = env.ONLY_FOR_CREATOR_MODE;
|
|
Environment.ENABLE_UNSAFE_EVAL = env.ENABLE_UNSAFE_EVAL;
|
|
Environment.MAX_PHOTO_SIZE = env.MAX_PHOTO_SIZE;
|
|
Environment.PROCESS_LINKS = env.PROCESS_LINKS;
|
|
Environment.LOCALES_DIR = env.LOCALES_DIR;
|
|
Localization.configure(env.LOCALES_DIR);
|
|
|
|
Environment.RATE_LIMIT_FALLBACK_POLICY = env.RATE_LIMIT_FALLBACK_POLICY;
|
|
Environment.IMAGE_HANDLE_POLICY = env.IMAGE_HANDLE_POLICY;
|
|
Environment.IMAGE_HANDLE_FALLBACK_POLICY = env.IMAGE_HANDLE_FALLBACK_POLICY;
|
|
|
|
Environment.BRAVE_SEARCH_API_KEY = env.BRAVE_SEARCH_API_KEY;
|
|
Environment.OPEN_WEATHER_MAP_API_KEY = env.OPEN_WEATHER_MAP_API_KEY;
|
|
|
|
Environment.FILE_TOOLS_ROOT_DIR = env.FILE_TOOLS_ROOT_DIR
|
|
? path.resolve(env.FILE_TOOLS_ROOT_DIR)
|
|
: undefined;
|
|
|
|
Environment.ENABLE_FS_TOOLS = env.ENABLE_FS_TOOLS ?? false;
|
|
|
|
Environment.DEFAULT_AI_PROVIDER = env.DEFAULT_AI_PROVIDER;
|
|
|
|
Environment.envSystemPrompt = env.SYSTEM_PROMPT;
|
|
Environment.envRankerToolPrompt = env.RANKER_TOOL_PROMPT;
|
|
Environment.SYSTEM_PROMPT = env.SYSTEM_PROMPT;
|
|
Environment.RANKER_TOOL_PROMPT = env.RANKER_TOOL_PROMPT;
|
|
Environment.USE_NAMES_IN_PROMPT = env.USE_NAMES_IN_PROMPT;
|
|
Environment.USE_SYSTEM_PROMPT = env.USE_SYSTEM_PROMPT;
|
|
Environment.SEND_TIME_TOOK = env.SEND_TIME_TOOK ?? false;
|
|
|
|
Environment.ENABLE_PYTHON_INTERPRETER = env.ENABLE_PYTHON_INTERPRETER ?? false;
|
|
|
|
Environment.OLLAMA_API_KEY = env.OLLAMA_API_KEY;
|
|
Environment.OLLAMA_ADDRESS = env.OLLAMA_ADDRESS;
|
|
Environment.OLLAMA_CHAT_MODEL = env.OLLAMA_CHAT_MODEL;
|
|
Environment.OLLAMA_IMAGE_MODEL = env.OLLAMA_IMAGE_MODEL ?? env.OLLAMA_CHAT_MODEL;
|
|
Environment.OLLAMA_THINK_MODEL = env.OLLAMA_THINK_MODEL ?? env.OLLAMA_CHAT_MODEL;
|
|
Environment.OLLAMA_AUDIO_MODEL = env.OLLAMA_AUDIO_MODEL ?? env.OLLAMA_CHAT_MODEL;
|
|
Environment.OLLAMA_EMBEDDING_MODEL = env.OLLAMA_EMBEDDING_MODEL;
|
|
Environment.OLLAMA_RAG_CHUNK_SIZE = env.OLLAMA_RAG_CHUNK_SIZE;
|
|
Environment.OLLAMA_RAG_CHUNK_OVERLAP = Math.min(env.OLLAMA_RAG_CHUNK_OVERLAP, Math.max(1, env.OLLAMA_RAG_CHUNK_SIZE - 1));
|
|
Environment.OLLAMA_RAG_TOP_K = env.OLLAMA_RAG_TOP_K;
|
|
Environment.OLLAMA_RAG_MAX_CONTEXT_CHARS = env.OLLAMA_RAG_MAX_CONTEXT_CHARS;
|
|
Environment.OLLAMA_RAG_MIN_SCORE = env.OLLAMA_RAG_MIN_SCORE;
|
|
Environment.OLLAMA_RAG_MAX_ARCHIVE_FILES = env.OLLAMA_RAG_MAX_ARCHIVE_FILES;
|
|
Environment.OLLAMA_RAG_MAX_ARCHIVE_BYTES = env.OLLAMA_RAG_MAX_ARCHIVE_BYTES;
|
|
Environment.OLLAMA_RAG_MAX_ARCHIVE_DEPTH = env.OLLAMA_RAG_MAX_ARCHIVE_DEPTH;
|
|
Environment.OLLAMA_MAX_CONCURRENT_REQUESTS = env.OLLAMA_MAX_CONCURRENT_REQUESTS;
|
|
|
|
Environment.MISTRAL_API_KEY = env.MISTRAL_API_KEY;
|
|
Environment.MISTRAL_MODEL = env.MISTRAL_MODEL;
|
|
Environment.MISTRAL_TRANSCRIPTION_MODEL = env.MISTRAL_TRANSCRIPTION_MODEL;
|
|
Environment.MISTRAL_TTS_MODEL = env.MISTRAL_TTS_MODEL;
|
|
Environment.MISTRAL_TTS_VOICE_ID = env.MISTRAL_TTS_VOICE_ID;
|
|
Environment.MISTRAL_MAX_CONCURRENT_REQUESTS = env.MISTRAL_MAX_CONCURRENT_REQUESTS;
|
|
|
|
Environment.OPENAI_BASE_URL = env.OPENAI_BASE_URL;
|
|
Environment.OPENAI_API_KEY = env.OPENAI_API_KEY;
|
|
Environment.OPENAI_MODEL = env.OPENAI_MODEL;
|
|
Environment.OPENAI_IMAGE_MODEL = env.OPENAI_IMAGE_MODEL;
|
|
Environment.OPENAI_TRANSCRIPTION_MODEL = env.OPENAI_TRANSCRIPTION_MODEL;
|
|
Environment.OPENAI_TTS_MODEL = env.OPENAI_TTS_MODEL;
|
|
Environment.OPENAI_TTS_VOICE = env.OPENAI_TTS_VOICE;
|
|
Environment.OPENAI_TTS_INSTRUCTIONS = env.OPENAI_TTS_INSTRUCTIONS;
|
|
Environment.OPENAI_MAX_CONCURRENT_REQUESTS = env.OPENAI_MAX_CONCURRENT_REQUESTS;
|
|
}
|
|
|
|
static load(): void {
|
|
const rawEnv = Environment.readConfigSource();
|
|
|
|
const startupEnv = StartupEnvSchema.parse(rawEnv);
|
|
const runtimeEnv = RuntimeEnvSchema.parse(rawEnv);
|
|
|
|
Environment.applyStartupEnv(startupEnv);
|
|
Environment.applyRuntimeEnv(runtimeEnv);
|
|
|
|
Environment.refreshSystemPrompt();
|
|
Environment.refreshRankerToolPrompt();
|
|
|
|
Environment.lastEnvMtimeMs = Environment.getFileMtimeMs(Environment.ENV_FILE_PATH);
|
|
Environment.lastSystemPromptMtimeMs = Environment.getFileMtimeMs(Environment.getSystemPromptPath());
|
|
Environment.lastRankerToolPromptMtimeMs = Environment.getFileMtimeMs(Environment.getRankerToolPromptPath());
|
|
}
|
|
|
|
static reloadRuntimeConfigIfChanged(): void {
|
|
try {
|
|
const envMtimeMs = Environment.getFileMtimeMs(Environment.ENV_FILE_PATH);
|
|
const systemPromptMtimeMs = Environment.getFileMtimeMs(Environment.getSystemPromptPath());
|
|
const rankerToolPromptMtimeMs = Environment.getFileMtimeMs(Environment.getRankerToolPromptPath());
|
|
|
|
const envChanged = envMtimeMs !== Environment.lastEnvMtimeMs;
|
|
const systemPromptChanged = systemPromptMtimeMs !== Environment.lastSystemPromptMtimeMs;
|
|
const rankerToolPromptChanged = rankerToolPromptMtimeMs !== Environment.lastRankerToolPromptMtimeMs;
|
|
|
|
Localization.reloadIfChanged();
|
|
|
|
if (!envChanged && !systemPromptChanged) {
|
|
return;
|
|
}
|
|
|
|
if (envChanged) {
|
|
const rawEnv = Environment.readConfigSource();
|
|
const runtimeEnv = RuntimeEnvSchema.parse(rawEnv);
|
|
|
|
Environment.applyRuntimeEnv(runtimeEnv);
|
|
Environment.refreshSystemPrompt();
|
|
Environment.refreshRankerToolPrompt();
|
|
Environment.lastEnvMtimeMs = envMtimeMs;
|
|
}
|
|
|
|
if (systemPromptChanged) {
|
|
Environment.refreshSystemPrompt();
|
|
Environment.lastSystemPromptMtimeMs = systemPromptMtimeMs;
|
|
}
|
|
|
|
if (rankerToolPromptChanged) {
|
|
Environment.refreshRankerToolPrompt();
|
|
Environment.lastRankerToolPromptMtimeMs = rankerToolPromptMtimeMs;
|
|
}
|
|
} catch (e) {
|
|
appLogger.child("environment").error("runtime_reload.failed", {error: e instanceof Error ? e : String(e)});
|
|
}
|
|
}
|
|
|
|
static setOnlyForCreatorMode(enable: boolean): void {
|
|
this.ONLY_FOR_CREATOR_MODE = enable;
|
|
}
|
|
|
|
static setBraveSearchApiKey(apiKey: string | undefined): void {
|
|
this.BRAVE_SEARCH_API_KEY = apiKey;
|
|
}
|
|
|
|
static setOpenWeatherMapApiKey(openWeatherMapApiKey: string | undefined): void {
|
|
this.OPEN_WEATHER_MAP_API_KEY = openWeatherMapApiKey;
|
|
}
|
|
|
|
static setFileToolsRootDir(rootDir: string | undefined): void {
|
|
this.FILE_TOOLS_ROOT_DIR = rootDir ? path.resolve(rootDir) : undefined;
|
|
}
|
|
|
|
static setSystemPrompt(prompt: string | undefined): void {
|
|
this.SYSTEM_PROMPT = prompt;
|
|
}
|
|
|
|
static setUseNamesInPrompt(use: boolean): void {
|
|
this.USE_NAMES_IN_PROMPT = use;
|
|
}
|
|
|
|
static setUseSystemPrompt(use: boolean): void {
|
|
this.USE_SYSTEM_PROMPT = use;
|
|
}
|
|
|
|
static setSendTimeTook(send: boolean): void {
|
|
this.SEND_TIME_TOOK = send;
|
|
}
|
|
|
|
static setAdmins(admins: Set<number>): void {
|
|
this.ADMIN_IDS = admins;
|
|
}
|
|
|
|
static async addAdmin(id: number): Promise<boolean> {
|
|
const has = this.ADMIN_IDS.has(id);
|
|
|
|
if (!has) {
|
|
this.ADMIN_IDS.add(id);
|
|
const {saveData} = await import("../db/database.js");
|
|
await saveData();
|
|
}
|
|
|
|
return !has;
|
|
}
|
|
|
|
static async removeAdmin(id: number): Promise<boolean> {
|
|
const has = this.ADMIN_IDS.has(id);
|
|
|
|
if (has) {
|
|
this.ADMIN_IDS.delete(id);
|
|
const {saveData} = await import("../db/database.js");
|
|
await saveData();
|
|
}
|
|
|
|
return has;
|
|
}
|
|
|
|
static setMuted(muted: Set<number>): void {
|
|
this.MUTED_IDS = muted;
|
|
}
|
|
|
|
static async addMute(id: number): Promise<boolean> {
|
|
if (this.MUTED_IDS.has(id)) {
|
|
return false;
|
|
}
|
|
|
|
this.MUTED_IDS.add(id);
|
|
const {saveData} = await import("../db/database.js");
|
|
await saveData();
|
|
return true;
|
|
}
|
|
|
|
static async removeMute(id: number): Promise<boolean> {
|
|
if (!this.MUTED_IDS.has(id)) {
|
|
return false;
|
|
}
|
|
|
|
this.MUTED_IDS.delete(id);
|
|
const {saveData} = await import("../db/database.js");
|
|
await saveData();
|
|
return true;
|
|
}
|
|
|
|
static setAnswers(answers: Answers): void {
|
|
this.ANSWERS = answers;
|
|
}
|
|
|
|
static setOllamaApiKey(key: string | undefined): void {
|
|
this.OLLAMA_API_KEY = key;
|
|
}
|
|
|
|
static setOllamaAddress(address: string | undefined): void {
|
|
this.OLLAMA_ADDRESS = address;
|
|
}
|
|
|
|
static setOllamaModel(ollamaModel: string): void {
|
|
this.OLLAMA_CHAT_MODEL = ollamaModel;
|
|
}
|
|
|
|
static setOllamaThinkModel(ollamaThinkModel: string): void {
|
|
this.OLLAMA_THINK_MODEL = ollamaThinkModel;
|
|
}
|
|
|
|
static setOllamaImageModel(ollamaImageModel: string): void {
|
|
this.OLLAMA_IMAGE_MODEL = ollamaImageModel;
|
|
}
|
|
|
|
static setMistralApiKey(newMistralApiKey: string | undefined): void {
|
|
this.MISTRAL_API_KEY = newMistralApiKey;
|
|
}
|
|
|
|
static setMistralModel(newModel: string): void {
|
|
this.MISTRAL_MODEL = newModel;
|
|
}
|
|
|
|
static setMistralTranscriptionModel(newModel: string): void {
|
|
this.MISTRAL_TRANSCRIPTION_MODEL = newModel;
|
|
}
|
|
|
|
static setMistralTtsModel(newModel: string): void {
|
|
this.MISTRAL_TTS_MODEL = newModel;
|
|
}
|
|
|
|
static setOpenAIBaseUrl(newAIBaseUrl: string | undefined): void {
|
|
this.OPENAI_BASE_URL = newAIBaseUrl;
|
|
}
|
|
|
|
static setOpenAIApiKey(newAIApiKey: string | undefined): void {
|
|
this.OPENAI_API_KEY = newAIApiKey;
|
|
}
|
|
|
|
static setOpenAIModel(newModel: string): void {
|
|
this.OPENAI_MODEL = newModel;
|
|
}
|
|
|
|
static setOpenAIImageModel(newImageModel: string): void {
|
|
this.OPENAI_IMAGE_MODEL = newImageModel;
|
|
}
|
|
|
|
static setOpenAITranscriptionModel(newModel: string): void {
|
|
this.OPENAI_TRANSCRIPTION_MODEL = newModel;
|
|
}
|
|
|
|
static setOpenAITtsModel(newModel: string): void {
|
|
this.OPENAI_TTS_MODEL = newModel;
|
|
}
|
|
}
|