/newbot إلى @BotFather",
+ "instruction-3": "3. اختر اسمًا واسم مستخدم لروبوتك.",
+ "instruction-4": "4. انسخ رمز واجهة برمجة التطبيقات الذي تتلقاه.",
+ },
+ step2: {
+ title: "الخطوة الثانية: قم بتوصيل روبوتك.",
+ description:
+ "الصق رمز API الذي تلقيته من @BotFather، ثم اختر مساحة عمل افتراضية لجهازك التابع للروبوت للدردشة فيها.",
+ "bot-token": "رمز الرمز (Token)",
+ "default-workspace": "مساحة العمل الافتراضية",
+ "no-workspace": "لا توجد مساحات عمل متاحة. سيتم إنشاء مساحة عمل جديدة.",
+ connecting: "التحميل...",
+ "connect-bot": "روبوت الاتصال",
+ },
+ security: {
+ title: "إعدادات الأمان الموصى بها",
+ description:
+ 'لزيادة الأمان، قم بتكوين هذه الإعدادات في حساب "@BotFather".',
+ "disable-groups": "– منع إضافة الروبوتات إلى المجموعات",
+ "disable-inline": "– منع استخدام الروبوت في عمليات البحث المباشرة.",
+ "obscure-username":
+ "استخدم اسم مستخدم روبوت غير تقليدي لتقليل فرص اكتشافه.",
+ },
+ "toast-enter-token": "الرجاء إدخال رمز البوت.",
+ "toast-connect-failed": "فشل الاتصال بالروبوت.",
+ },
+ connected: {
+ status: "متصل",
+ "status-disconnected":
+ "غير متصل — قد يكون الرمز منتهي الصلاحية أو غير صالح",
+ "placeholder-token": "ألصق رمز البوت الجديد...",
+ reconnect: "إعادة الاتصال",
+ workspace: "مساحة العمل",
+ "bot-link": "رابط الروبوت",
+ "voice-response": "رد صوتي",
+ disconnecting: "قطع الاتصال...",
+ disconnect: "افصل",
+ "voice-text-only": "النص فقط",
+ "voice-mirror": "مرآة (يرد الصوت عند إرسال المستخدم له)",
+ "voice-always": "يرجى تضمين تسجيل صوتي (إرسال تسجيل صوتي مع كل رد).",
+ "toast-disconnect-failed": "فشل فصل الوحدة الآلية.",
+ "toast-reconnect-failed": "فشل إعادة الاتصال بالروبوت.",
+ "toast-voice-failed": "فشلت عملية تحديث وضع الصوت.",
+ "toast-approve-failed": "فشل في الموافقة على المستخدم.",
+ "toast-deny-failed": "فشل في رفض طلب المستخدم.",
+ "toast-revoke-failed": "فشل إلغاء صلاحية المستخدم.",
+ },
+ users: {
+ "pending-title": "معلق على الموافقة",
+ "pending-description":
+ "المستخدمون في انتظار التحقق. قارن رمز المطابقة المعروض هنا بالرمز المعروض في محادثتهم على تطبيق Telegram.",
+ "approved-title": "المستخدمون المعتمدون",
+ "approved-description":
+ "المستخدمون الذين تم منحهم الإذن للتواصل مع روبوتك.",
+ user: "المستخدم",
+ "pairing-code": "رمز التوفيق",
+ "no-pending": "لا توجد طلبات معلقة.",
+ "no-approved": "لا يوجد مستخدمون معتمدون.",
+ unknown: "غير معروف",
+ approve: "الموافقة",
+ deny: "رفض",
+ revoke: "إلغاء",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/cs/common.js b/frontend/src/locales/cs/common.js
index e2643d2f..bfc52322 100644
--- a/frontend/src/locales/cs/common.js
+++ b/frontend/src/locales/cs/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Název pracovního prostoru",
- user: "Uživatel",
selection: "Výběr modelu",
saving: "Ukládání...",
save: "Uložit změny",
@@ -113,6 +112,10 @@ const TRANSLATIONS = {
"your-account": "Váš účet",
"import-item": "Importovat položku",
},
+ channels: "Kanály",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -196,18 +199,12 @@ const TRANSLATIONS = {
title: "Režim chatu",
chat: {
title: "Chat",
- description:
- "poskytne odpovědi založené na obecných znalostech LLM a kontextu dokumentu, který je k dispozici.@BotFather, postupujte podle pokynů a zkopírujte API token.",
+ "open-botfather": "Spusťte BotFather",
+ "instruction-1": "1. Otevřete odkaz nebo naskenujte QR kód",
+ "instruction-2":
+ "2. Pošlete /newbot na adresu @BotFather",
+ "instruction-3":
+ "3. Vyberte jméno a uživatelské jméno pro svého robota.",
+ "instruction-4": "4. Zkopírujte API token, který obdržíte.",
+ },
+ step2: {
+ title: "Krok 2: Připojte svého robota",
+ description:
+ "Vložte API token, který jste obdrželi od účtu @BotFather, a vyberte výchozí pracovní prostor, se kterým bude váš bot komunikovat.",
+ "bot-token": "Token Bot",
+ "default-workspace": "Výchozí pracovní prostor",
+ "no-workspace":
+ "Nejsou k dispozici žádné pracovní prostory. Bude vytvořeno nové.",
+ connecting: "Připojování...",
+ "connect-bot": "Bot pro připojení",
+ },
+ security: {
+ title: "Doporučené bezpečnostní nastavení",
+ description:
+ "Pro zvýšení bezpečnosti, nakonfigurujte tyto nastavení v účtu @BotFather.",
+ "disable-groups": "— Zabránit přidávání bot do skupin",
+ "disable-inline":
+ "— Zabraňte použití robota při vyhledávání v reálném čase.",
+ "obscure-username":
+ "Použijte neobvyklé uživatelské jméno pro robota, abyste snížili jeho snadnou identifikovatelnost.",
+ },
+ "toast-enter-token": "Prosím, zadejte token pro robota.",
+ "toast-connect-failed": "Nedaří se připojit k botovi.",
+ },
+ connected: {
+ status: "Spojené",
+ "status-disconnected": "Neaktivní – token může být prošlý nebo neplatný",
+ "placeholder-token": "Vložte nový token pro robota...",
+ reconnect: "Znovu se spojit",
+ workspace: "Pracovní prostor",
+ "bot-link": "Odkaz na robota",
+ "voice-response": "Reakce na hlasový vstup",
+ disconnecting: "Odpojování...",
+ disconnect: "Odpojit",
+ "voice-text-only": "Pouze text",
+ "voice-mirror":
+ "Zrcadlo (odpovězte hlasem, když uživatel pošle hlasovou zprávu)",
+ "voice-always":
+ "Vždy uveďte zvukový záznam (odesílejte zvukový záznam ke každé odpovědi)",
+ "toast-disconnect-failed": "Nepodařilo se odpojit automat.",
+ "toast-reconnect-failed": "Nedaří se znovu navázat spojení s botem.",
+ "toast-voice-failed": "Nepodařilo se aktualizovat hlasový režim.",
+ "toast-approve-failed": "Neúspěšné schválení uživatele.",
+ "toast-deny-failed": "Nezucceededo v odmítnutí uživatele.",
+ "toast-revoke-failed": "Nezdařilo se zrušit uživatelskou účet.",
+ },
+ users: {
+ "pending-title": "Čeká na schválení",
+ "pending-description":
+ "Uživatelé, kteří čekají na ověření. Porovnejte kód pro spárování, který je zde uveden, s tím, který je zobrazen v jejich chatu na Telegramu.",
+ "approved-title": "Schválení uživatelů",
+ "approved-description":
+ "Uživatelé, kteří byli schváleni pro komunikaci s vaším botem.",
+ user: "Uživatel",
+ "pairing-code": "Kód pro párování",
+ "no-pending": "Žádné čekající požadavky",
+ "no-approved": "Žádní registrovaní uživatelé",
+ unknown: "Neznámé",
+ approve: "Schválit",
+ deny: "Odmítnout",
+ revoke: "Zrušit",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/da/common.js b/frontend/src/locales/da/common.js
index 618dbb3b..edf967af 100644
--- a/frontend/src/locales/da/common.js
+++ b/frontend/src/locales/da/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Navn på arbejdsområder",
- user: "Bruger",
selection: "Modelvalg",
saving: "Gemmer...",
save: "Gem ændringer",
@@ -107,6 +106,10 @@ const TRANSLATIONS = {
"your-account": "Dit konti",
"import-item": "Importeret vare",
},
+ channels: "Kanaler",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -182,18 +185,12 @@ const TRANSLATIONS = {
title: "Chat-tilstand",
chat: {
title: "Chat",
- description:
- 'vil give svar baseret på LLM\'s generelle viden og den relevante kontekst fra dokumentet. Du skal bruge kommandoen "@agent" for at bruge værktøjerne.',
},
query: {
title: "Forespørgsel",
- description:
- "vil give svar kun, hvis dokumentets kontekst er fundet.@BotFather, følg instruktionerne, og kopier API-tokenet.",
+ "open-botfather": "Åbn BotFather",
+ "instruction-1": "1. Åbn linket eller scann QR-koden",
+ "instruction-2":
+ "2. Send /newbot til @BotFather",
+ "instruction-3": "3. Vælg et navn og et brugernavn til din bot",
+ "instruction-4": "4. Kopier API-tokenet, du modtager.",
+ },
+ step2: {
+ title: "Trin 2: Forbind din bot",
+ description:
+ "Indsæt API-tokenet, du modtog fra @BotFather, og vælg et standard-arbejdsområde, hvor din bot kan kommunikere.",
+ "bot-token": "Bot-token",
+ "default-workspace": "Standardarbejdsområde",
+ "no-workspace":
+ "Ingen ledige arbejdsområder. Et nyt vil blive oprettet.",
+ connecting: "Forbindes...",
+ "connect-bot": "Connect Bot",
+ },
+ security: {
+ title: "Anbefalede sikkerhedsindstillinger",
+ description:
+ "For yderligere sikkerhed, kan du konfigurere disse indstillinger via @BotFather.",
+ "disable-groups": "— Forhindre tilføjelse af bots til grupper",
+ "disable-inline":
+ "— Forhindr brugen af bot i søgninger direkte i søgefeltet",
+ "obscure-username":
+ "Brug et brugernavn til en bot, der ikke er åbenlyst, for at reducere synligheden.",
+ },
+ "toast-enter-token": "Vær venligst opført et bot-token.",
+ "toast-connect-failed": "Kunne ikke etablere forbindelse med botten.",
+ },
+ connected: {
+ status: "Forbundet",
+ "status-disconnected":
+ "Afbrudt – tokenet kan være udløbet eller ugyldigt",
+ "placeholder-token": "Indsæt nyt bot-token...",
+ reconnect: "Genopslå",
+ workspace: "Arbejdsområde",
+ "bot-link": "Bot-link",
+ "voice-response": "Stemmebesvarelse",
+ disconnecting: "Afbryde...",
+ disconnect: "Afbryde",
+ "voice-text-only": "Kun tekst",
+ "voice-mirror": "Spejl (svar med stemme, når brugeren sender en stemme)",
+ "voice-always":
+ "Sørg altid for at inkludere en lydbesked (send lyd sammen med hvert svar).",
+ "toast-disconnect-failed": "Kunne ikke afbryde robotten.",
+ "toast-reconnect-failed":
+ "Kunne ikke genoprette forbindelsen med botten.",
+ "toast-voice-failed": "Kunne ikke opdatere stemmemodus.",
+ "toast-approve-failed": "Mislykkedes med at godkende bruger.",
+ "toast-deny-failed": "Kunne ikke afvise brugeren.",
+ "toast-revoke-failed": "Kunne ikke annullere brugerens adgang.",
+ },
+ users: {
+ "pending-title": "Afventer godkendelse",
+ "pending-description":
+ "Brugere, der venter på at blive verificeret. Sammenlign den kode, der vises her, med den, der vises i deres Telegram-chat.",
+ "approved-title": "Godkendte brugere",
+ "approved-description":
+ "Brugere, der er blevet godkendt til at kommunikere med din bot.",
+ user: "Bruger",
+ "pairing-code": "Kombinationskode",
+ "no-pending": "Ingen igangværende anmodninger",
+ "no-approved": "Ingen godkendte brugere",
+ unknown: "Ukendt",
+ approve: "Godkend",
+ deny: "Afvise",
+ revoke: "Annullere",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/de/common.js b/frontend/src/locales/de/common.js
index 9f87172b..f6a9e4d3 100644
--- a/frontend/src/locales/de/common.js
+++ b/frontend/src/locales/de/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Namen der Workspaces",
- user: "Benutzer",
selection: "Modellauswahl",
saving: "Speichern...",
save: "Änderungen speichern",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Ihr Konto",
"import-item": "Artikel importieren",
},
+ channels: "Kanäle",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -189,18 +192,12 @@ const TRANSLATIONS = {
title: "Chat-Modus",
chat: {
title: "Chat",
- description:
- "wird Antworten basierend auf dem allgemeinen Wissen des LLM und dem relevanten Kontext aus den Dokumenten und liefern. /newbot an @BotFather, befolgen Sie die Anweisungen und kopieren Sie den API-Token.",
+ "open-botfather": "Öffnen Sie BotFather",
+ "instruction-1": "1. Öffnen Sie den Link oder scannen Sie den QR-Code",
+ "instruction-2":
+ "2. Senden Sie /newbot an @BotFather",
+ "instruction-3":
+ "3. Wählen Sie einen Namen und einen Benutzernamen für Ihren Bot aus.",
+ "instruction-4": "4. Kopieren Sie den API-Token, den Sie erhalten.",
+ },
+ step2: {
+ title: "Schritt 2: Verbinden Sie Ihren Bot",
+ description:
+ "Fügen Sie den API-Token ein, den Sie von @BotFather erhalten haben, und wählen Sie einen Standard-Arbeitsbereich für Ihren Bot aus, mit dem er kommunizieren soll.",
+ "bot-token": "Bot-Token",
+ "default-workspace": "Standardarbeitsbereich",
+ "no-workspace":
+ "Keine verfügbaren Arbeitsbereiche. Ein neuer Bereich wird erstellt.",
+ connecting: "Verbinde...",
+ "connect-bot": "Connect-Bot",
+ },
+ security: {
+ title: "Empfohlene Sicherheitseinstellungen",
+ description:
+ "Für zusätzliche Sicherheit, konfigurieren Sie diese Einstellungen über @BotFather.",
+ "disable-groups":
+ "– Verhinderung der automatisierten Anmeldung von Bots in Gruppen",
+ "disable-inline":
+ "– Verhindern Sie die Verwendung von Bots in der Inline-Suche",
+ "obscure-username":
+ "Verwenden Sie einen Benutzernamen für den Bot, der nicht offensichtlich ist, um die Auffindbarkeit zu reduzieren.",
+ },
+ "toast-enter-token": "Bitte geben Sie einen Bot-Token ein.",
+ "toast-connect-failed":
+ "Verbindung zum Bot konnte nicht hergestellt werden.",
+ },
+ connected: {
+ status: "Verbunden",
+ "status-disconnected":
+ "Abgekoppelt – Token möglicherweise abgelaufen oder ungültig",
+ "placeholder-token": "Neuen Bot-Token einfügen...",
+ reconnect: "Wiederherstellen",
+ workspace: "Arbeitsbereich",
+ "bot-link": "Link",
+ "voice-response": "Sprachantwort",
+ disconnecting: "Abmelden...",
+ disconnect: "Abkoppeln",
+ "voice-text-only": "Nur Text",
+ "voice-mirror":
+ "Echo (Antworten mit Sprache, wenn der Benutzer Sprache sendet)",
+ "voice-always":
+ "Bitte immer Sprachnachrichten senden (Audio mit jeder Antwort hinzufügen)",
+ "toast-disconnect-failed":
+ "Es konnte nicht erfolgreich die Verbindung zum Bot trennen.",
+ "toast-reconnect-failed":
+ "Verbindung zum Bot konnte nicht hergestellt werden.",
+ "toast-voice-failed":
+ "Fehlgeschlagen bei der Aktualisierung des Sprachmodus.",
+ "toast-approve-failed": "Benutzer konnte nicht autorisiert werden.",
+ "toast-deny-failed": "Nicht in der Lage, den Benutzer abzuweisen.",
+ "toast-revoke-failed":
+ "Fehlgeschlagener Versuch, das Benutzerkonto zu deaktivieren.",
+ },
+ users: {
+ "pending-title": "Warte auf Genehmigung",
+ "pending-description":
+ "Benutzer, die noch verifiziert werden müssen. Vergleichen Sie den hier angezeigten Pairing-Code mit dem, der in ihrem Telegram-Chat angezeigt wird.",
+ "approved-title": "Benutzer mit Genehmigung",
+ "approved-description":
+ "Nutzer, denen die Erlaubnis erteilt wurde, mit Ihrem Bot zu kommunizieren.",
+ user: "Benutzer",
+ "pairing-code": "Paarcode",
+ "no-pending": "Keine ausstehenden Anfragen",
+ "no-approved": "Keine autorisierten Benutzer",
+ unknown: "Unbekannt",
+ approve: "Genehmigen",
+ deny: "Leugnen",
+ revoke: "Aufheben",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js
index 72d67a2e..e227d01b 100644
--- a/frontend/src/locales/en/common.js
+++ b/frontend/src/locales/en/common.js
@@ -50,7 +50,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Workspace Name",
- user: "User",
selection: "Model Selection",
saving: "Saving...",
save: "Save changes",
@@ -111,6 +110,10 @@ const TRANSLATIONS = {
contact: "Contact Support",
"browser-extension": "Browser Extension",
"mobile-app": "AnythingLLM Mobile",
+ channels: "Channels",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -195,18 +198,12 @@ const TRANSLATIONS = {
title: "Chat mode",
automatic: {
title: "Auto",
- description:
- "will automatically use tools if the model and provider support native tool calling./newbot to @BotFather, follow the prompts, and copy the API token.",
+ "open-botfather": "Open BotFather",
+ "instruction-1": "1. Open the link or scan the QR code",
+ "instruction-2":
+ "2. Send /newbot to @BotFather",
+ "instruction-3": "3. Choose a name and username for your bot",
+ "instruction-4": "4. Copy the API token you receive",
+ },
+ step2: {
+ title: "Step 2: Connect your bot",
+ description:
+ "Paste the API token you received from @BotFather and select a default workspace for your bot to chat with.",
+ "bot-token": "Bot Token",
+ "default-workspace": "Default Workspace",
+ "no-workspace": "No available workspaces. A new one will be created.",
+ connecting: "Connecting...",
+ "connect-bot": "Connect Bot",
+ },
+ security: {
+ title: "Recommended Security Settings",
+ description:
+ "For additional security, configure these settings in @BotFather.",
+ "disable-groups": "— Prevent adding bot to groups",
+ "disable-inline": "— Prevent bot from being used in inline search",
+ "obscure-username":
+ "Use a non-obvious bot handle username to reduce discoverability",
+ },
+ "toast-enter-token": "Please enter a bot token.",
+ "toast-connect-failed": "Failed to connect bot.",
+ },
+ connected: {
+ status: "Connected",
+ "status-disconnected": "Disconnected — token may be expired or invalid",
+ "placeholder-token": "Paste new bot token...",
+ reconnect: "Reconnect",
+ workspace: "Workspace",
+ "bot-link": "Bot Link",
+ "voice-response": "Voice Response",
+ disconnecting: "Disconnecting...",
+ disconnect: "Disconnect",
+ "voice-text-only": "Text only",
+ "voice-mirror": "Mirror (reply with voice when user sends voice)",
+ "voice-always": "Always voice (send audio with every reply)",
+ "toast-disconnect-failed": "Failed to disconnect bot.",
+ "toast-reconnect-failed": "Failed to reconnect bot.",
+ "toast-voice-failed": "Failed to update voice mode.",
+ "toast-approve-failed": "Failed to approve user.",
+ "toast-deny-failed": "Failed to deny user.",
+ "toast-revoke-failed": "Failed to revoke user.",
+ },
+ users: {
+ "pending-title": "Pending Approval",
+ "pending-description":
+ "Users waiting to be verified. Match the pairing code shown here with the one displayed in their Telegram chat.",
+ "approved-title": "Approved Users",
+ "approved-description":
+ "Users who have been approved to chat with your bot.",
+ user: "User",
+ "pairing-code": "Pairing Code",
+ "no-pending": "No pending requests",
+ "no-approved": "No approved users",
+ unknown: "Unknown",
+ approve: "Approve",
+ deny: "Deny",
+ revoke: "Revoke",
+ },
+ },
security: {
title: "Security",
multiuser: {
@@ -834,7 +906,6 @@ const TRANSLATIONS = {
source_count_other: "{{count}} references",
document: "Document",
similarity_match: "match",
- pause_tts_speech_message: "Pause TTS speech of message",
fork: "Fork",
delete: "Delete",
cancel: "Cancel",
@@ -865,7 +936,6 @@ const TRANSLATIONS = {
normal: "Normal",
large: "Large",
tools: "Tools",
- browse: "Browse",
text_size_label: "Text Size",
select_model: "Select Model",
slash_commands: "Slash Commands",
diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js
index 160ef0d7..7aeb28ec 100644
--- a/frontend/src/locales/es/common.js
+++ b/frontend/src/locales/es/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Nombre de los espacios de trabajo",
- user: "Usuario",
selection: "Selección de modelo",
saving: "Guardando...",
save: "Guardar cambios",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Su cuenta",
"import-item": "Importar artículo",
},
+ channels: "Canales",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -190,18 +193,12 @@ const TRANSLATIONS = {
title: "Modo de chat",
chat: {
title: "Chat",
- description:
- 'proporcionará respuestas basándose en el conocimiento general del LLM y en el contexto del documento que se encuentre disponible. Para utilizar las herramientas, deberá utilizar el comando "@agent".',
},
query: {
title: "Consulta",
- description:
- 'proporcionará respuestas solo si se encuentra el contexto del documento.@BotFather, siga las instrucciones y copie el token de la API.",
+ "open-botfather": "Iniciar BotFather",
+ "instruction-1": "1. Abra el enlace o escanee el código QR.",
+ "instruction-2":
+ "2. Enviar /newbot a @BotFather",
+ "instruction-3":
+ "3. Elija un nombre y un nombre de usuario para su bot.",
+ "instruction-4": "4. Copie el token de la API que reciba.",
+ },
+ step2: {
+ title: "Paso 2: Conecte su bot.",
+ description:
+ "Copia el token de API que recibiste de @BotFather y selecciona un espacio de trabajo predeterminado para que tu bot pueda comunicarse.",
+ "bot-token": "Token de Bot",
+ "default-workspace": "Espacio de trabajo predeterminado",
+ "no-workspace":
+ "No hay espacios de trabajo disponibles. Se creará uno nuevo.",
+ connecting: "Conectando...",
+ "connect-bot": "Bot de conexión",
+ },
+ security: {
+ title: "Configuraciones de seguridad recomendadas",
+ description:
+ "Para una mayor seguridad, configure estas opciones a través de @BotFather.",
+ "disable-groups": "— Evitar que se añadan bots a los grupos",
+ "disable-inline":
+ "— Evitar que los bots se utilicen en búsquedas dentro de la página.",
+ "obscure-username":
+ "Utiliza un nombre de usuario para el bot que no sea obvio para reducir su visibilidad.",
+ },
+ "toast-enter-token": "Por favor, introduzca un token de bot.",
+ "toast-connect-failed": "No se pudo establecer la conexión con el bot.",
+ },
+ connected: {
+ status: "Conectado",
+ "status-disconnected":
+ "Desconectado — el token puede estar caducado o ser inválido.",
+ "placeholder-token": "Pegar nuevo token de bot...",
+ reconnect: "Restablecer la conexión",
+ workspace: "Espacio de trabajo",
+ "bot-link": "Enlace a bot",
+ "voice-response": "Respuesta por voz",
+ disconnecting: "Desconectando...",
+ disconnect: "Desconectar",
+ "voice-text-only": "Solo texto",
+ "voice-mirror":
+ "Espejo (responder con voz cuando el usuario envía una grabación de voz)",
+ "voice-always":
+ "Siempre incluir una grabación de voz (enviar audio con cada respuesta).",
+ "toast-disconnect-failed": "No se pudo desconectar el robot.",
+ "toast-reconnect-failed":
+ "No se pudo restablecer la conexión con el bot.",
+ "toast-voice-failed": "No se pudo actualizar el modo de voz.",
+ "toast-approve-failed": "No se pudo aprobar el usuario.",
+ "toast-deny-failed": "No se pudo negar la solicitud del usuario.",
+ "toast-revoke-failed": "No se pudo revocar el acceso del usuario.",
+ },
+ users: {
+ "pending-title": "Sujeto a aprobación",
+ "pending-description":
+ "Usuarios que están esperando la verificación. Compara el código de emparejamiento que se muestra aquí con el que aparece en su conversación de Telegram.",
+ "approved-title": "Usuarios autorizados",
+ "approved-description":
+ "Usuarios que han sido aprobados para comunicarse con tu bot.",
+ user: "Usuario",
+ "pairing-code": "Código de combinación",
+ "no-pending": "No hay solicitudes pendientes.",
+ "no-approved": "Usuarios no autorizados",
+ unknown: "Desconocido",
+ approve: "Aprobar",
+ deny: "Negar",
+ revoke: "Revocar",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/et/common.js b/frontend/src/locales/et/common.js
index 3e1b8187..f205266c 100644
--- a/frontend/src/locales/et/common.js
+++ b/frontend/src/locales/et/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Tööruumide nimi",
- user: "Kasutaja",
selection: "Mudeli valik",
saving: "Salvestan…",
save: "Salvesta muudatused",
@@ -105,6 +104,10 @@ const TRANSLATIONS = {
"your-account": "Teie konto",
"import-item": "Importeeritud toode",
},
+ channels: "Kaasavad",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -186,18 +189,12 @@ const TRANSLATIONS = {
title: "Vestlusrežiim",
chat: {
title: "Vestlus",
- description:
- 'teenab vastuseid, kasutades LLM-i üldist teadmist ja dokumentide konteksti, mida on leitav.
Selleks peate kasutama käsku "@agent".',
},
query: {
title: "Päring",
- description:
- 'teenib vastuseid ainult__, kui dokumendi kontekst on leitud. Vajate kasutama käesu "agent", et kasutada tööriime.',
},
automatic: {
title: "Automaailm",
- description:
- 'kasutab automaatselt tööriistu, kui mudel ja pakkuja toetavad native tööriistade kasutamist.
Kui native tööriistade kasutamine pole toetatud, peate kasutama käsku "@agent", et tööriiste kasutada.',
},
},
history: {
@@ -807,7 +804,6 @@ const TRANSLATIONS = {
see_less: "Näita vähem",
see_more: "Vaata rohkem",
tools: "Vahendid",
- browse: "Sirva",
text_size_label: "Teksti suurus",
select_model: "Valige mudel",
sources: "Allikasid",
@@ -820,7 +816,6 @@ const TRANSLATIONS = {
edit: "Redigeerimine",
publish: "Avaldada",
stop_generating: "Lõpeta vastuste genereerimine",
- pause_tts_speech_message: "Peata sõna-sünteesi (TTS) rääkimine sõnumis",
slash_commands: "Lihtsasti kasutatavad käsud",
agent_skills: "Agentide oskused",
manage_agent_skills: "Halda agentide oskusi",
@@ -976,6 +971,84 @@ const TRANSLATIONS = {
"Sa ei ole täidetud ühtegi tööruumi.\nPäringu tööruumiks, palun pööra teie administraatorile.",
goToWorkspace: 'Mine tööruumiks "{{workspace}}"',
},
+ telegram: {
+ title: "Telegrami bot",
+ description:
+ "Ühendage oma AnythingLLM instants Telegramiga, et saaksite vestleda oma tööruumidega igast seadmist.",
+ setup: {
+ step1: {
+ title: "1. samm: Looge oma Telegrami bot",
+ description:
+ "Ava Telegramis konto @BotFather, saat /newbot aadressile @BotFather, järgige juhiseid ja kopeerige API-token.",
+ "open-botfather": "Ava BotFather",
+ "instruction-1": "1. Avage link või skannige QR-kood",
+ "instruction-2":
+ "2. Saada /newbot aadressile @BotFather",
+ "instruction-3": "3. Valige oma botile nimi ja kasutajanimi.",
+ "instruction-4": "4. Kopeerige API-token, mida teile antakse.",
+ },
+ step2: {
+ title: "2. Samuti ühendage oma bot",
+ description:
+ "Kleepige API-token, mis teil on saanud kasutaja @BotFatherilt, ning valige oma botile vaikimõistmine.",
+ "bot-token": "Bot token",
+ "default-workspace": "Vaikimisi kasutatav tööruum",
+ "no-workspace":
+ "Praegu pole saadaval vaba töökohti. Ühe uue töökohtade loomine on plaanis.",
+ connecting: "Ühendamine...",
+ "connect-bot": "Ühendusrobott",
+ },
+ security: {
+ title: "Soovitavad turvameetmed",
+ description:
+ "Lisaks turvalisusele, konfigureerige need seaded @BotFatheris.",
+ "disable-groups": "— Ennetada, et botid ei lisataks gruppi",
+ "disable-inline": "— Vältida, et bot kasutaks otsingut reaalajas.",
+ "obscure-username":
+ "Kasutage mitteolivaid kasutajanime, et vähendada avastamise võimalust.",
+ },
+ "toast-enter-token": "Palun sisestage bot'i token.",
+ "toast-connect-failed": "Bot ei suutnud ühendust tehes.",
+ },
+ connected: {
+ status: "Ühendatud",
+ "status-disconnected":
+ "Vabandus, toet – toet võib olla kehtimatuna või kehtima lõppenud",
+ "placeholder-token": "Sisestage uus bot'i token...",
+ reconnect: "Taastada ühendus",
+ workspace: "Tööruum",
+ "bot-link": "Bot link",
+ "voice-response": "Häälreaktsioon",
+ disconnecting: "Ühendus katkestatud...",
+ disconnect: "Ühenduse katkestamine",
+ "voice-text-only": "Tekst ainult",
+ "voice-mirror":
+ "Helisüsteem (vastake häältega, kui kasutaja kasutab helifunktsiooni)",
+ "voice-always": "Alati lisage hääl (saada helifail koos iga vastusega)",
+ "toast-disconnect-failed": "Impeer ei õnnestunud seadistada.",
+ "toast-reconnect-failed": "Bot ei suutnud ühendust taastada.",
+ "toast-voice-failed": "Ärkimõõtmeid ei õnnestunud uuendada.",
+ "toast-approve-failed": "Kasutaja kinnitamise ebaõnnestumine.",
+ "toast-deny-failed": "Ei suutnud kasutaja kohta infot väita.",
+ "toast-revoke-failed": "Ebaõnnestuti kasutaja konto kustutamises.",
+ },
+ users: {
+ "pending-title": "Ootea faasis, ootamas heakskiitu",
+ "pending-description":
+ "Kasutajad, kes ootavad kinnitamist. Võrdige siin näidatud vastuvõtusümboli koos nende Telegrami vestluses näidatud sümboliga.",
+ "approved-title": "Heakskiidud kasutajad",
+ "approved-description":
+ "Kasutajad, kellele on antud lubadus teie botiga vestelda.",
+ user: "Kasutaja",
+ "pairing-code": "Koosamis kood",
+ "no-pending": "Hetkel pole ootamisel ühtegi taotlust",
+ "no-approved": "Pole heakskiidud kasutajaid",
+ unknown: "Tuntud pole",
+ approve: "Heakskiid",
+ deny: "Nõgata",
+ revoke: "Tingimata",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js
index a1c4ab19..dc015f0f 100644
--- a/frontend/src/locales/fa/common.js
+++ b/frontend/src/locales/fa/common.js
@@ -53,7 +53,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "نام فضای کار",
- user: "کاربر",
selection: "انتخاب مدل",
saving: "در حال ذخیره...",
save: "ذخیره تغییرات",
@@ -107,6 +106,10 @@ const TRANSLATIONS = {
"your-account": "حساب شما",
"import-item": "وارد کردن کالا",
},
+ channels: "کانالها",
+ "available-channels": {
+ telegram: "تلگرام",
+ },
},
login: {
"multi-user": {
@@ -181,18 +184,12 @@ const TRANSLATIONS = {
title: "حالت گفتگو",
chat: {
title: "گفتگو",
- description:
- "با استفاده از دانش عمومی مدل زبانی و اطلاعات موجود در سند، پاسخها را ارائه خواهد داد. برای استفاده از ابزارها، باید از دستور @agent استفاده کنید.",
},
query: {
title: "پرسوجو",
- description:
- "پاسخها را تنها در صورت یافتن زمینه سند ارائه میدهد. برای استفاده از ابزارها، باید از دستور @agent استفاده کنید.",
},
automatic: {
title: "خودرو",
- description:
- "اگر مدل و ارائهدهنده از فراخوانی ابزار به صورت پیشفرض پشتیبانی کنند، ابزارها بهطور خودکار استفاده خواهند شد.
در صورتی که فراخوانی ابزار به صورت پیشفرض پشتیبانی نشود، شما باید از دستور @agent برای استفاده از ابزارها استفاده کنید.",
},
},
history: {
@@ -743,7 +740,6 @@ const TRANSLATIONS = {
see_less: "کمی بیشتر",
see_more: "بیشتر",
tools: "ابزارها",
- browse: "جستجو",
text_size_label: "اندازه متن",
select_model: "انتخاب مدل",
sources: "منابع",
@@ -756,7 +752,6 @@ const TRANSLATIONS = {
edit: "ویرایش",
publish: "انتشار",
stop_generating: "متوقف کردن تولید پاسخ",
- pause_tts_speech_message: "مکث در پخش صدای متن",
slash_commands: "دستورات کوتاهشده",
agent_skills: "مهارتهای کارگزار",
manage_agent_skills: "مدیریت مهارتهای نمایندگان",
@@ -1012,6 +1007,84 @@ const TRANSLATIONS = {
"شما در حال حاضر به هیچ فضای کاری اختصاص نیافتهاید.\nلطفاً با مدیر خود تماس بگیرید تا دسترسی به یک فضای کار را درخواست کنید.",
goToWorkspace: 'به فضای کار "{{workspace}}" بروید',
},
+ telegram: {
+ title: "ربات تلگرام",
+ description:
+ "با اتصال نمونه AnythingLLM خود به تلگرام، میتوانید از هر دستگاهی با فضاهای کاری خود گفتگو کنید.",
+ setup: {
+ step1: {
+ title: "مرحله ۱: ایجاد ربات Telegram خود",
+ description:
+ "اپلیکیشن @BotFather را در تلگرام باز کنید، دستور /newbot را برای @BotFather ارسال کنید، دستورالعملها را دنبال کنید و توکن API را کپی کنید.",
+ "open-botfather": "شروع با ربات BotFather",
+ "instruction-1": "1. لینک را باز کنید یا کد QR را اسکن کنید",
+ "instruction-2":
+ "2. پیام /newbot را برای @BotFather ارسال کنید.",
+ "instruction-3": "3. یک نام و نام کاربری برای ربات خود انتخاب کنید.",
+ "instruction-4": "4. توکنی که دریافت میکنید را کپی کنید.",
+ },
+ step2: {
+ title: "مرحله دوم: اتصال ربات خود",
+ description:
+ "توکن API را که از @BotFather دریافت کردهاید، کپی کنید و یک فضای کاری پیشفرض را برای ربات خود انتخاب کنید تا بتواند با کاربران ارتباط برقرار کند.",
+ "bot-token": "توکن ربات",
+ "default-workspace": "فضای کاری پیشفرض",
+ "no-workspace":
+ "فضاهای کاری موجود نیست. یک فضای کاری جدید ایجاد خواهد شد.",
+ connecting: "در حال اتصال...",
+ "connect-bot": "اتصال ربات",
+ },
+ security: {
+ title: "تنظیمات امنیتی پیشنهادی",
+ description:
+ "برای افزایش امنیت، این تنظیمات را در حساب @BotFather پیکربندی کنید.",
+ "disable-groups": "— جلوگیری از اضافه کردن ربات به گروهها",
+ "disable-inline":
+ "— از استفاده رباتها در جستجوی درونصفحهای جلوگیری کنید.",
+ "obscure-username":
+ "از یک نام کاربری برای ربات که به راحتی قابل تشخیص نباشد، استفاده کنید تا میزان شناسایی آن را کاهش دهید.",
+ },
+ "toast-enter-token": "لطفاً یک توکن برای ربات وارد کنید.",
+ "toast-connect-failed": "عدم امکان اتصال ربات.",
+ },
+ connected: {
+ status: "اتصال یافته",
+ "status-disconnected":
+ "قطع شده – احتمال دارد توکن منقضی شده یا نامعتبر باشد",
+ "placeholder-token": "وارد کردن توکن جدید برای ربات...",
+ reconnect: "بازسازی ارتباط",
+ workspace: "فضای کاری",
+ "bot-link": "لینک ربات",
+ "voice-response": "پاسخ صوتی",
+ disconnecting: "قطع ارتباط...",
+ disconnect: "قطع ارتباط",
+ "voice-text-only": "فقط متن",
+ "voice-mirror": "بازتاب (پاسخگویی با صدا هنگام ارسال صدا توسط کاربر)",
+ "voice-always":
+ "همیشه، حتماً، یک صدای صوتی (ارسال فایل صوتی همراه با هر پاسخ)",
+ "toast-disconnect-failed": "عدم توانایی در قطع ارتباط با ربات.",
+ "toast-reconnect-failed": "عدم امکان برقراری ارتباط مجدد با ربات.",
+ "toast-voice-failed": "عدم امکان بهروزرسانی حالت صدا.",
+ "toast-approve-failed": "عدم تایید کاربر.",
+ "toast-deny-failed": "امکان رد درخواست کاربر وجود نداشت.",
+ "toast-revoke-failed": "امکان لغو کردن حساب کاربری وجود نداشت.",
+ },
+ users: {
+ "pending-title": "منتظر تایید",
+ "pending-description":
+ "کاربرانی که منتظر تایید هستند. کد تطبیقی که در اینجا نشان داده شده را با کد موجود در چت تلگرام خود مطابقت دهید.",
+ "approved-title": "کاربران تایید شده",
+ "approved-description": "کاربرانی که مجوز دارند با ربات شما گفتگو کنند.",
+ user: "کاربر",
+ "pairing-code": "کد جفتسازی",
+ "no-pending": "هیچ درخواست در حال انجام وجود ندارد.",
+ "no-approved": "کاربران تایید شده وجود ندارد",
+ unknown: "نامشخص",
+ approve: "تایید",
+ deny: "رد",
+ revoke: "اعلام لغو",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/findUnusedTranslations.mjs b/frontend/src/locales/findUnusedTranslations.mjs
index 06072ec2..9f5a45ea 100644
--- a/frontend/src/locales/findUnusedTranslations.mjs
+++ b/frontend/src/locales/findUnusedTranslations.mjs
@@ -54,12 +54,14 @@ function collectFiles(dir, results = []) {
const sourceFiles = collectFiles(FRONTEND_SRC);
// ---------------------------------------------------------------------------
-// 3. Scan source files for t() references (literal and dynamic)
+// 3. Scan source files for t() and i18nKey references (literal and dynamic)
// ---------------------------------------------------------------------------
const referencedKeys = new Set();
const tCallRegex = /\bt\(\s*["'`]([^"'`]+)["'`]/g;
const dynamicTCallRegex = /\bt\(\s*([a-zA-Z_$][a-zA-Z0-9_$.]*)\s*[,)]/g;
const templateTCallRegex = /\bt\(\s*`([^`]*\$\{[^`]*)`\s*[,)]/g;
+const i18nKeyRegex = /i18nKey=["'`]([^"'`]+)["'`]/g;
+const i18nKeyJsxRegex = /i18nKey=\{["'`]([^"'`]+)["'`]\}/g;
const dynamicUsages = [];
for (const file of sourceFiles) {
@@ -70,6 +72,14 @@ for (const file of sourceFiles) {
referencedKeys.add(match[1]);
}
+ while ((match = i18nKeyRegex.exec(content)) !== null) {
+ referencedKeys.add(match[1]);
+ }
+
+ while ((match = i18nKeyJsxRegex.exec(content)) !== null) {
+ referencedKeys.add(match[1]);
+ }
+
while ((match = dynamicTCallRegex.exec(content)) !== null) {
const arg = match[1];
if (/^["'`]/.test(arg)) continue;
diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js
index 72224709..789b3c64 100644
--- a/frontend/src/locales/fr/common.js
+++ b/frontend/src/locales/fr/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Nom des espaces de travail",
- user: "Utilisateur",
selection: "Sélection du modèle",
saving: "Enregistrement...",
save: "Enregistrer les modifications",
@@ -105,6 +104,10 @@ const TRANSLATIONS = {
"your-account": "Votre compte",
"import-item": "Importer",
},
+ channels: "Canaux",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -182,18 +185,12 @@ const TRANSLATIONS = {
title: "Mode de chat",
chat: {
title: "Chat",
- description:
- 'fournira des réponses en utilisant les connaissances générales du LLM et le contexte du document correspondant.
Vous devrez utiliser la commande "@agent" pour utiliser les outils.',
},
query: {
title: "Requête",
- description:
- "fournira des réponses uniquement si le contexte du document est trouvé.
Vous devrez utiliser la commande @agent pour utiliser les outils.",
},
automatic: {
title: "Voiture",
- description:
- "utilisera automatiquement les outils si le modèle et le fournisseur prennent en charge l'appel de outils natifs.
Si l'utilisation d'outils natifs n'est pas prise en charge, vous devrez utiliser la commande \"@agent\" pour utiliser les outils.",
},
},
history: {
@@ -747,7 +744,6 @@ const TRANSLATIONS = {
see_less: "Voir moins",
see_more: "Voir plus",
tools: "Outils",
- browse: "Parcourir",
text_size_label: "Taille du texte",
select_model: "Sélectionner le modèle",
sources: "Sources",
@@ -760,8 +756,6 @@ const TRANSLATIONS = {
edit: "Modifier",
publish: "Publier",
stop_generating: "Arrêtez de générer des réponses",
- pause_tts_speech_message:
- "Mettre en pause la lecture de la voix synthétique du message",
slash_commands: "Commandes abrégées",
agent_skills: "Compétences des agents",
manage_agent_skills: "Gérer les compétences des agents",
@@ -1016,6 +1010,87 @@ const TRANSLATIONS = {
"Vous n'êtes actuellement pas affecté à aucun espace de travail.\nPour accéder à un espace de travail, veuillez contacter votre administrateur.",
goToWorkspace: 'Aller à "{{workspace}}"',
},
+ telegram: {
+ title: "Bot Telegram",
+ description:
+ "Connectez votre instance de AnythingLLM à Telegram afin de pouvoir communiquer avec vos espaces de travail depuis n'importe quel appareil.",
+ setup: {
+ step1: {
+ title: "Étape 1 : Créez votre bot Telegram",
+ description:
+ "Ouvrez @BotFather sur Telegram, envoyez `/newbot` à @BotFather, suivez les instructions, et copiez le jeton API.",
+ "open-botfather": "Ouvrir BotFather",
+ "instruction-1": "1. Ouvrez le lien ou numérisez le code QR.",
+ "instruction-2":
+ "2. Envoyer /newbot à @BotFather",
+ "instruction-3":
+ "3. Choisissez un nom et un nom d'utilisateur pour votre bot.",
+ "instruction-4": "4. Copiez le jeton API que vous recevez.",
+ },
+ step2: {
+ title: "Étape 2 : Connectez votre bot",
+ description:
+ "Collez le jeton API que vous avez reçu de @BotFather et sélectionnez un espace de travail par défaut pour que votre bot puisse communiquer.",
+ "bot-token": "Token Bot",
+ "default-workspace": "Espace de travail par défaut",
+ "no-workspace":
+ "Il n'y a pas d'espaces de travail disponibles. Un nouvel espace sera créé.",
+ connecting: "Connexion...",
+ "connect-bot": "Bot de connexion",
+ },
+ security: {
+ title: "Paramètres de sécurité recommandés",
+ description:
+ "Pour une sécurité supplémentaire, configurez ces paramètres via @BotFather.",
+ "disable-groups": "— Empêcher l'ajout de bots aux groupes",
+ "disable-inline":
+ "— Empêcher l'utilisation de bots dans les recherches en ligne.",
+ "obscure-username":
+ "Utilisez un nom d'utilisateur de bot non évident pour réduire sa visibilité.",
+ },
+ "toast-enter-token": "Veuillez saisir un jeton de bot.",
+ "toast-connect-failed": "Échec de la connexion du bot.",
+ },
+ connected: {
+ status: "Connecté",
+ "status-disconnected":
+ "Non connecté – le jeton pourrait être expiré ou invalide.",
+ "placeholder-token": "Coller le nouveau jeton de bot...",
+ reconnect: "Reconnexion",
+ workspace: "Espace de travail",
+ "bot-link": "Lien vers le bot",
+ "voice-response": "Réponse vocale",
+ disconnecting: "Déconnexion...",
+ disconnect: "Déconnecter",
+ "voice-text-only": "Texte uniquement",
+ "voice-mirror":
+ "Écho (répondre par la voix lorsque l'utilisateur envoie une voix)",
+ "voice-always":
+ "Toujours inclure une voix (envoyer un enregistrement audio avec chaque réponse)",
+ "toast-disconnect-failed": "Échec de la déconnexion du robot.",
+ "toast-reconnect-failed": "Échec de la reconnexion du bot.",
+ "toast-voice-failed": "Impossible de mettre à jour le mode vocal.",
+ "toast-approve-failed": "Échec de la validation de l'utilisateur.",
+ "toast-deny-failed": "Impossible de refuser l'accès à l'utilisateur.",
+ "toast-revoke-failed": "Impossible de supprimer l'utilisateur.",
+ },
+ users: {
+ "pending-title": "En attente d'approbation",
+ "pending-description":
+ "Utilisateurs en attente de vérification. Correspondez le code de correspondance affiché ici avec celui qui apparaît dans leur conversation Telegram.",
+ "approved-title": "Utilisateurs autorisés",
+ "approved-description":
+ "Utilisateurs qui ont été autorisés à communiquer avec votre bot.",
+ user: "Utilisateur",
+ "pairing-code": "Code de correspondance",
+ "no-pending": "Aucune demande en cours",
+ "no-approved": "Aucun utilisateur autorisé",
+ unknown: "Inconnu",
+ approve: "Approuver",
+ deny: "Rejeter",
+ revoke: "Annuler",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/he/common.js b/frontend/src/locales/he/common.js
index 73ddf4de..049fb650 100644
--- a/frontend/src/locales/he/common.js
+++ b/frontend/src/locales/he/common.js
@@ -49,7 +49,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "שם סביבת העבודה",
- user: "משתמש",
selection: "בחירת מודל",
saving: "שומר...",
save: "שמור שינויים",
@@ -103,6 +102,10 @@ const TRANSLATIONS = {
"your-account": "החשבון שלך",
"import-item": "ייבוא פריט",
},
+ channels: "ערוצים",
+ "available-channels": {
+ telegram: "טלגרם",
+ },
},
login: {
"multi-user": {
@@ -184,18 +187,12 @@ const TRANSLATIONS = {
title: "מצב צ'אט",
chat: {
title: "צ'אט",
- description:
- 'יוכל לספק תשובות בהתבסס על הידע הכללי של ה-LLM ועל ההקשר הרלוונטי מתוך המסמך. ו-\nתצטרכו להשתמש בפקודה "@agent" כדי להשתמש בכלי.',
},
query: {
title: "שאילתה",
- description:
- "יספק תשובות רקבמידה ויהיה ניתן למצוא הקשר של המסמך.
תצטרכו להשתמש בפקודה @agent כדי להשתמש בכלי.",
},
automatic: {
title: "רכב",
- description:
- 'הכלי ישתמש באופן אוטומטי בכלים אם המודל והספק תומכים בהם.
אם אין תמיכה בכלים מקומיים, תצטרכו להשתמש בפקודה "@agent" כדי להשתמש בכלים.',
},
},
history: {
@@ -811,7 +808,6 @@ const TRANSLATIONS = {
see_less: "ראה פחות",
see_more: "לראות עוד",
tools: "כלים",
- browse: "גלו",
text_size_label: "גודל הטקסט",
select_model: "בחר מודל",
sources: "מקורות",
@@ -824,8 +820,6 @@ const TRANSLATIONS = {
edit: "עריכה",
publish: "להוציא לאור",
stop_generating: "הפסיקו ליצור תגובה",
- pause_tts_speech_message:
- "השהייה של קריאת טקסט באמצעות תוכנת TTS (Text-to-Speech)",
slash_commands: "פקודות קיצור",
agent_skills: "כישורים של סוכן",
manage_agent_skills: "ניהול מיומנויות של סוכנים",
@@ -982,6 +976,80 @@ const TRANSLATIONS = {
"אינך מוקצה לכל סביבת עבודה.\nיש ליצור קשר עם המנהל שלך כדי לבקש גישה לסביבת עבודה.",
goToWorkspace: 'עבור לסביבת עבודה "{{workspace}}"',
},
+ telegram: {
+ title: "בוט של טלגרם",
+ description:
+ "חברו את ההתקנה של AnythingLLM ל-Telegram, כך שתוכלו לתקשר עם סביבות העבודה שלכם ממכשיר כלשהו.",
+ setup: {
+ step1: {
+ title: "שלב 1: צרו את הבוט שלכם ב-Telegram",
+ description:
+ "פתח את ב-Telegram, שלח לכתובת @BotFather, עקוב אחר ההוראות, והעתק את מזהה ה-API.",
+ "open-botfather": "פתוח את BotFather",
+ "instruction-1": "1. פתחו את הקישור או סרקו את קוד ה-QR",
+ "instruction-2":
+ "2. שלחו את /newbot לכתובת @BotFather",
+ "instruction-3": "3. בחרו שם וכינוי משתמש עבור הבוט שלכם",
+ "instruction-4": "4. העתק את מזהה ה-API שקיבלת.",
+ },
+ step2: {
+ title: "שלב 2: חברו את הבוט שלכם",
+ description:
+ "הדבק את טוקן ה-API שקיבלת מחשבון @BotFather ובחר את חלל העבודה הראשי עבור הבוט שלך, כדי שיוכל לתקשר.",
+ "bot-token": "טוקן בוט",
+ "default-workspace": "סביבת עבודה ברירת מחדל",
+ "no-workspace": "אין מקומות עבודה זמינים. ייקבע מקום עבודה חדש.",
+ connecting: "חיבור...",
+ "connect-bot": "צ'אטבוט",
+ },
+ security: {
+ title: "הגדרות אבטחה מומלצות",
+ description:
+ "לנוחיות נוספת, יש לבצע את ההגדרות הללו דרך חשבון ה-@BotFather.",
+ "disable-groups": "— למנוע הוספת רובוטים לקבוצות",
+ "disable-inline": "– למנוע שימוש בבוט בחיפוש ישיר",
+ "obscure-username":
+ "השתמש בשם משתמש של בוט שאינו בולט, כדי להקטין את הסיכוי שהוא יימצא.",
+ },
+ "toast-enter-token": "אנא הזן את טוקן הבוט.",
+ "toast-connect-failed": "לא הצליח להתחבר עם הבוט.",
+ },
+ connected: {
+ status: "מחובר",
+ "status-disconnected": "נתקע – הטוקן עשוי להיות פג או לא תקין",
+ "placeholder-token": "הדבק את מפתח הבוט החדש...",
+ reconnect: "שוב קשר",
+ workspace: "חלל עבודה",
+ "bot-link": "קישור לבוט",
+ "voice-response": "תגובה קולית",
+ disconnecting: "ניתוק...",
+ disconnect: "ניתוק",
+ "voice-text-only": "טקסט בלבד",
+ "voice-mirror": "משקף (להגיב בקול כאשר המשתמש שולח קול)",
+ "voice-always": "יש לציין תמיד (לשלוח קבצי אודיו עם כל תגובה)",
+ "toast-disconnect-failed": "לא הצלחתי לבטל את פעולת הבוט.",
+ "toast-reconnect-failed": "לא הצליח לשחזר את הבוט.",
+ "toast-voice-failed": "לא הצליח לעדכן את מצב השמע.",
+ "toast-approve-failed": "לא ניתן לאשר את המשתמש.",
+ "toast-deny-failed": "לא הצליח לסרב לבקשה של המשתמש.",
+ "toast-revoke-failed": "לא הצלחתי לבטל את החשבון של המשתמש.",
+ },
+ users: {
+ "pending-title": "נמצא בהמתנה לאישור",
+ "pending-description":
+ "משתמשים הממתינים לאישור. יש להתאים את הקוד שמוצג כאן עם הקוד המוצג בשיחה שלהם ב-Telegram.",
+ "approved-title": "משתמשים מורשים",
+ "approved-description": "משתמשים שאושרו לנהל שיחה עם הבוט שלכם.",
+ user: "משתמש",
+ "pairing-code": "קוד התאמה",
+ "no-pending": "אין בקשות בתהליך",
+ "no-approved": "אין משתמשים מורשים",
+ unknown: "לא ידוע",
+ approve: "אישור",
+ deny: "לדחות",
+ revoke: "בטל",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/it/common.js b/frontend/src/locales/it/common.js
index c22b60e1..06be0d34 100644
--- a/frontend/src/locales/it/common.js
+++ b/frontend/src/locales/it/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Nome delle aree di lavoro",
- user: "Utente",
selection: "Selezione del modello",
saving: "Salvo...",
save: "Salva modifiche",
@@ -107,6 +106,10 @@ const TRANSLATIONS = {
"your-account": "Il tuo account",
"import-item": "Importa articolo",
},
+ channels: "Canali",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -183,18 +186,12 @@ const TRANSLATIONS = {
title: "Modalità chat",
chat: {
title: "Chat",
- description:
- "fornirà risposte basate sulla conoscenza generale del modello LLM e sul contesto del documento e che è disponibile.
Per utilizzare gli strumenti, sarà necessario utilizzare il comando @agent.",
},
query: {
title: "Query",
- description:
- 'fornirà risposte solo se il contesto del documento viene trovato. Per utilizzare gli strumenti, sarà necessario utilizzare il comando "@agent".',
},
automatic: {
title: "Auto",
- description:
- 'utilizzerà automaticamente gli strumenti se il modello e il fornitore supportano la chiamata nativa agli strumenti.
Se la chiamata nativa agli strumenti non è supportata, sarà necessario utilizzare il comando "@agent" per utilizzare gli strumenti.',
},
},
history: {
@@ -756,7 +753,6 @@ const TRANSLATIONS = {
see_less: "Visualizza meno",
see_more: "Visualizza altro",
tools: "Strumenti",
- browse: "Naviga",
text_size_label: "Dimensione del testo",
select_model: "Seleziona il modello",
sources: "Fonti",
@@ -769,8 +765,6 @@ const TRANSLATIONS = {
edit: "Modifica",
publish: "Pubblicare",
stop_generating: "Interrompi la generazione della risposta",
- pause_tts_speech_message:
- "Mettere in pausa la lettura vocale del messaggio",
slash_commands: "Comandi abbreviati",
agent_skills: "Competenze dell'agente",
manage_agent_skills: "Gestire le competenze degli agenti",
@@ -1041,6 +1035,87 @@ const TRANSLATIONS = {
"Non sei assegnato a nessuno spazio di lavoro.\nContatta il tuo amministratore per richiedere l'accesso a uno spazio di lavoro.",
goToWorkspace: 'Vai allo spazio di lavoro "{{workspace}}"',
},
+ telegram: {
+ title: "Bot per Telegram",
+ description:
+ "Collega la tua istanza di AnythingLLM a Telegram in modo da poter chattare con i tuoi spazi di lavoro da qualsiasi dispositivo.",
+ setup: {
+ step1: {
+ title: "Passo 1: Crea il tuo bot Telegram",
+ description:
+ "Apri il bot @BotFather su Telegram, invia `/newbot` a @BotFather, segui le istruzioni e copia il token API.",
+ "open-botfather": "Avvia BotFather",
+ "instruction-1": "1. Apri il link o scansiona il codice QR",
+ "instruction-2":
+ "2. Invia /newbot a @BotFather",
+ "instruction-3": "3. Scegli un nome e un nome utente per il tuo bot.",
+ "instruction-4": "4. Copiare il token API che riceverete",
+ },
+ step2: {
+ title: "Passo 2: Collegare il tuo bot",
+ description:
+ "Incolla il token API che hai ricevuto da @BotFather e seleziona uno spazio di lavoro predefinito per il tuo bot, in modo che possa comunicare.",
+ "bot-token": "Token Bot",
+ "default-workspace": "Spazio di lavoro predefinito",
+ "no-workspace":
+ "Non sono disponibili spazi di lavoro. Ne verrà creato uno nuovo.",
+ connecting: "Connessione...",
+ "connect-bot": "Bot di connessione",
+ },
+ security: {
+ title: "Impostazioni di sicurezza consigliate",
+ description:
+ "Per una maggiore sicurezza, configurare queste impostazioni tramite @BotFather.",
+ "disable-groups": "— Impedire l'aggiunta di bot ai gruppi",
+ "disable-inline": "— Impedire l'uso di bot nelle ricerche inline",
+ "obscure-username":
+ "Utilizza un nome utente per il bot che non sia ovvio, per ridurre la sua visibilità.",
+ },
+ "toast-enter-token": "Si prega di inserire un token per il bot.",
+ "toast-connect-failed":
+ "Impossibile stabilire la connessione con il bot.",
+ },
+ connected: {
+ status: "Collegato",
+ "status-disconnected":
+ "Non connesso – il token potrebbe essere scaduto o non valido",
+ "placeholder-token": "Incolla il nuovo token del bot...",
+ reconnect: "Riconnettersi",
+ workspace: "Spazio di lavoro",
+ "bot-link": "Link al bot",
+ "voice-response": "Risposta vocale",
+ disconnecting: "Disconnessione...",
+ disconnect: "Disconnetti",
+ "voice-text-only": "Testo solo",
+ "voice-mirror":
+ "Specchio (risposta vocale quando l'utente invia un messaggio vocale)",
+ "voice-always":
+ "Invia sempre un messaggio vocale (registra un audio con ogni risposta).",
+ "toast-disconnect-failed": "Impossibile disconnettere il bot.",
+ "toast-reconnect-failed":
+ "Impossibile ristabilire la connessione con il bot.",
+ "toast-voice-failed": "Impossibile aggiornare la modalità vocale.",
+ "toast-approve-failed": "Impossibile approvare l'utente.",
+ "toast-deny-failed": "Impossibile negare l'accesso all'utente.",
+ "toast-revoke-failed": "Impossibile revocare l'accesso dell'utente.",
+ },
+ users: {
+ "pending-title": "In attesa di approvazione",
+ "pending-description":
+ "Utenti in attesa di verifica. Confrontare il codice di abbinamento visualizzato qui con quello visualizzato nella loro chat di Telegram.",
+ "approved-title": "Utenti approvati",
+ "approved-description":
+ "Utenti che sono stati approvati per chattare con il vostro bot.",
+ user: "Utente",
+ "pairing-code": "Codice di abbinamento",
+ "no-pending": "Non ci sono richieste in sospeso.",
+ "no-approved": "Nessun utente autorizzato",
+ unknown: "Sconosciuto",
+ approve: "Approvare",
+ deny: "Negare",
+ revoke: "Annullare",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/ja/common.js b/frontend/src/locales/ja/common.js
index a2c106de..0948e6e6 100644
--- a/frontend/src/locales/ja/common.js
+++ b/frontend/src/locales/ja/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "ワークスペース名",
- user: "ユーザー",
selection: "モデル選択",
saving: "保存中...",
save: "変更を保存",
@@ -105,6 +104,10 @@ const TRANSLATIONS = {
"your-account": "あなたのアカウント",
"import-item": "輸入品",
},
+ channels: "チャンネル",
+ "available-channels": {
+ telegram: "テレグラム",
+ },
},
login: {
"multi-user": {
@@ -180,18 +183,12 @@ const TRANSLATIONS = {
title: "チャットモード",
chat: {
title: "チャット",
- description:
- "LLMの一般的な知識と、関連するドキュメントの文脈に基づいて、回答を提供します。ツールを使用するには、`@agent`コマンドを使用する必要があります。",
},
query: {
title: "クエリ",
- description:
- "該当する情報が見つかった場合、回答をのみ提供します。ツールを使用するには、@agentコマンドを使用する必要があります。",
},
automatic: {
title: "自動車",
- description:
- "ネイティブなツール呼び出しをサポートしている場合、モデルとプロバイダーが自動的にツールを使用します。
ネイティブなツール呼び出しがサポートされていない場合は、@agentコマンドを使用してツールを使用する必要があります。",
},
},
history: {
@@ -736,7 +733,6 @@ const TRANSLATIONS = {
see_less: "詳細を見る",
see_more: "詳細を見る",
tools: "道具",
- browse: "閲覧",
text_size_label: "文字サイズ",
select_model: "モデルを選択",
sources: "出典",
@@ -749,7 +745,6 @@ const TRANSLATIONS = {
edit: "編集",
publish: "出版",
stop_generating: "応答の生成を停止する",
- pause_tts_speech_message: "メッセージのテキスト読み上げ機能を一時停止する",
slash_commands: "スラッシュコマンド",
agent_skills: "エージェントのスキル",
manage_agent_skills: "エージェントのスキル管理",
@@ -1017,6 +1012,83 @@ const TRANSLATIONS = {
"現在、あなたはどのワークスペースにも割り当てられていません。\nワークスペースへのアクセスを要求するには、管理者にお問い合わせください。",
goToWorkspace: 'ワークスペースに移動 "{{workspace}}"',
},
+ telegram: {
+ title: "テレグラムボット",
+ description:
+ "AnyLLM のインスタンスを Telegram に接続することで、あらゆるデバイスからワークスペースとのチャットが可能になります。",
+ setup: {
+ step1: {
+ title: "ステップ1:Telegramボットを作成する",
+ description:
+ "Telegramの@BotFatherを開き、「/newbot」と入力して@BotFatherに送信します。指示に従い、APIトークンをコピーしてください。",
+ "open-botfather": "BotFather を起動する",
+ "instruction-1": "1. リンクを開くか、QRコードをスキャンする",
+ "instruction-2":
+ "2. 「」/「newbot」を「」で、「」@「BotFather」に送信してください。",
+ "instruction-3": "3. 独自の名前とユーザー名をボットに設定してください",
+ "instruction-4": "4. 受け取ったAPIトークンをコピーしてください",
+ },
+ step2: {
+ title: "ステップ2:ボットとの接続",
+ description:
+ "@BotFatherから受け取ったAPIトークンを貼り付け、ボットとのチャットに使用するデフォルトのワークスペースを選択してください。",
+ "bot-token": "ボット トークン",
+ "default-workspace": "デフォルトのワークスペース",
+ "no-workspace":
+ "利用可能な作業スペースがありません。新しい作業スペースが作成されます。",
+ connecting: "接続中...",
+ "connect-bot": "コネクトボット",
+ },
+ security: {
+ title: "推奨されるセキュリティ設定",
+ description:
+ "追加のセキュリティのため、@BotFatherでこれらの設定を設定してください。",
+ "disable-groups": "— グループへのボットの追加を防止",
+ "disable-inline": "— インライン検索でのボットの使用を防止",
+ "obscure-username":
+ "目立たないユーザー名をbotに使用することで、発見されにくくする。",
+ },
+ "toast-enter-token": "ボットのトークンを入力してください。",
+ "toast-connect-failed": "ボットとの接続に失敗しました。",
+ },
+ connected: {
+ status: "接続されている",
+ "status-disconnected":
+ "通信エラー - トークンが無効または期限切れになっている可能性があります",
+ "placeholder-token": "新しいボットのトークンを貼り付け...",
+ reconnect: "再接続",
+ workspace: "作業スペース",
+ "bot-link": "ボットへのリンク",
+ "voice-response": "音声応答",
+ disconnecting: "接続を解除...",
+ disconnect: "接続を解除する",
+ "voice-text-only": "テキストのみ",
+ "voice-mirror": "(ユーザーが音声で送信した場合、音声で返信)",
+ "voice-always": "常に音声メッセージ(返信ごとに音声データを送信)",
+ "toast-disconnect-failed": "ボットとの接続を解除できませんでした。",
+ "toast-reconnect-failed": "ボットとの再接続に失敗しました。",
+ "toast-voice-failed": "音声モードの更新に失敗しました。",
+ "toast-approve-failed": "ユーザーの承認に失敗しました。",
+ "toast-deny-failed": "ユーザーからの拒否を拒否できませんでした。",
+ "toast-revoke-failed": "ユーザーの権限停止に失敗。",
+ },
+ users: {
+ "pending-title": "承認待ち",
+ "pending-description":
+ "本人情報の確認待ちのユーザー。ここに表示されているペアリングコードを、彼らがTelegramで表示しているコードと照合してください。",
+ "approved-title": "承認されたユーザー",
+ "approved-description":
+ "あなたのボットとのチャットを許可されたユーザー。",
+ user: "利用者",
+ "pairing-code": "組み合わせコード",
+ "no-pending": "処理中のリクエストはありません",
+ "no-approved": "承認されたユーザーはいません",
+ unknown: "不明",
+ approve: "承認",
+ deny: "否定",
+ revoke: "無効化する",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/ko/common.js b/frontend/src/locales/ko/common.js
index 8dc361a9..a0aaae12 100644
--- a/frontend/src/locales/ko/common.js
+++ b/frontend/src/locales/ko/common.js
@@ -50,7 +50,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "워크스페이스 이름",
- user: "사용자",
selection: "모델 선택",
saving: "저장 중...",
save: "저장",
@@ -104,6 +103,10 @@ const TRANSLATIONS = {
"your-account": "당신의 계정",
"import-item": "수입 품목",
},
+ channels: "채널",
+ "available-channels": {
+ telegram: "텔레그램",
+ },
},
login: {
"multi-user": {
@@ -185,18 +188,12 @@ const TRANSLATIONS = {
title: "채팅 모드",
chat: {
title: "채팅",
- description:
- "LLM의 일반적인 지식과 관련 문맥 정보를 활용하여 답변을 제공합니다. 도구를 사용하려면 @agent 명령어를 사용해야 합니다.",
},
query: {
title: "쿼리",
- description:
- "문서 맥락이 발견되면 에만 답변을 제공합니다.
도구를 사용하려면 @agent 명령을 사용해야 합니다.",
},
automatic: {
title: "자동",
- description:
- "모델과 제공업체가 네이티브 도구 호출을 지원하는 경우, 자동으로 도구를 사용합니다.
네이티브 도구 호출이 지원되지 않는 경우, 도구를 사용하려면 @agent 명령을 사용해야 합니다.",
},
},
history: {
@@ -821,7 +818,6 @@ const TRANSLATIONS = {
see_less: "더 보기",
see_more: "더 보기",
tools: "도구",
- browse: "검색",
text_size_label: "글자 크기",
select_model: "모델 선택",
sources: "출처",
@@ -834,7 +830,6 @@ const TRANSLATIONS = {
edit: "수정",
publish: "출판",
stop_generating: "응답 생성 중단",
- pause_tts_speech_message: "메시지의 텍스트 음성 변환(TTS) 기능을 일시 중지",
slash_commands: "슬래시 명령어",
agent_skills: "에이전트의 역량",
manage_agent_skills: "에이전트 역량 관리",
@@ -995,6 +990,82 @@ const TRANSLATIONS = {
"현재 워크스페이스에 할당되지 않았습니다.\n워크스페이스에 대한 접근을 요청하려면 관리자에게 문의하세요.",
goToWorkspace: '워크스페이스로 이동 "{{workspace}}"',
},
+ telegram: {
+ title: "텔레그램 봇",
+ description:
+ "AnyLLM 인스턴스를 Telegram과 연결하여, 어떤 기기에서든 워크스페이스와 채팅할 수 있도록 합니다.",
+ setup: {
+ step1: {
+ title: "1단계: 텔레그램 봇을 만드세요",
+ description:
+ "텔레그램에서 @BotFather를 열고, /newbot를 @BotFather에게 보내고, 안내에 따라 진행하여 API 토큰을 복사합니다.",
+ "open-botfather": "BotFather 시작",
+ "instruction-1": "1. 링크를 열거나 QR 코드를 스캔",
+ "instruction-2":
+ "2. /newbot를 @BotFather에게 전송",
+ "instruction-3": "3. 봇의 이름과 사용자 이름을 선택하세요.",
+ "instruction-4": "4. 받은 API 토큰을 복사합니다.",
+ },
+ step2: {
+ title: "2단계: 봇을 연결합니다.",
+ description:
+ "@BotFather로부터 받은 API 토큰을 복사하여, 봇이 채팅할 기본 워크스페이스를 선택하세요.",
+ "bot-token": "봇 토큰",
+ "default-workspace": "기본 워크스페이스",
+ "no-workspace":
+ "사용 가능한 작업 공간이 없습니다. 새로운 작업 공간이 생성될 것입니다.",
+ connecting: "연결 중...",
+ "connect-bot": "연결 봇",
+ },
+ security: {
+ title: "권장 보안 설정",
+ description:
+ "추가적인 보안을 위해, @BotFather에서 다음 설정을 구성해 주세요.",
+ "disable-groups": "— 그룹에 봇 추가 방지",
+ "disable-inline": "— 인라인 검색에서 봇 사용을 방지",
+ "obscure-username":
+ "자명한 봇 사용자 이름을 피하고, 발견 가능성을 줄이기 위해",
+ },
+ "toast-enter-token": "봇 토큰을 입력해 주세요.",
+ "toast-connect-failed": "봇 연결에 실패했습니다.",
+ },
+ connected: {
+ status: "연결된",
+ "status-disconnected":
+ "연결되지 않음 – 토큰이 만료되었거나 유효하지 않을 수 있습니다",
+ "placeholder-token": "새로운 봇 토큰을 붙여넣으세요...",
+ reconnect: "재 연결",
+ workspace: "업무 공간",
+ "bot-link": "봇 링크",
+ "voice-response": "음성 응답",
+ disconnecting: "연결 해제 중...",
+ disconnect: "연결 해제",
+ "voice-text-only": "텍스트만",
+ "voice-mirror": "(사용자가 음성으로 응답하면, 음성으로 답변)",
+ "voice-always": "항상 음성 메시지 (답변과 함께 오디오 전송)",
+ "toast-disconnect-failed": "봇과의 연결을 해제하는 데 실패했습니다.",
+ "toast-reconnect-failed": "봇과의 연결에 실패했습니다.",
+ "toast-voice-failed": "음성 모드 업데이트에 실패했습니다.",
+ "toast-approve-failed": "사용자 승인에 실패했습니다.",
+ "toast-deny-failed": "사용자에게 거부 권한을 부여하지 못함.",
+ "toast-revoke-failed": "사용자 계정 삭제에 실패했습니다.",
+ },
+ users: {
+ "pending-title": "승인 대기 중",
+ "pending-description":
+ "승인 대기 중인 사용자. 여기 표시된 매칭 코드를 자신의 Telegram 채팅에서 표시된 코드로 일치시켜 주세요.",
+ "approved-title": "승인된 사용자",
+ "approved-description": "당신의 봇과 대화할 수 있도록 승인된 사용자.",
+ user: "사용자",
+ "pairing-code": "코드 매칭",
+ "no-pending": "처리 중인 요청이 없습니다.",
+ "no-approved": "승인된 사용자가 없습니다",
+ unknown: "알 수 없음",
+ approve: "승인",
+ deny: "부인",
+ revoke: "취소",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/lt/common.js b/frontend/src/locales/lt/common.js
index 1b78be29..bc635e33 100644
--- a/frontend/src/locales/lt/common.js
+++ b/frontend/src/locales/lt/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Darbo srities pavadinimas",
- user: "Vartotojas",
selection: "Modelio pasirinkimas",
saving: "Saugoma...",
save: "Išsaugoti pakeitimus",
@@ -112,6 +111,10 @@ const TRANSLATIONS = {
contact: "Susisiekti su pagalba",
"browser-extension": "Naršyklės plėtinys",
"mobile-app": "AnythingLLM mobiliesiems",
+ channels: "Kanalai",
+ "available-channels": {
+ telegram: "„Telegram“",
+ },
},
login: {
"multi-user": {
@@ -195,18 +198,12 @@ const TRANSLATIONS = {
title: "Pokalbio režimas",
automatic: {
title: "Auto",
- description:
- "automatiškai naudos įrankius, jei modelis ir tiekėjas palaiko natūralų įrankių iškvietimą.
Jei natūralus įrankių iškvietimas nepalaikomas, norėdami naudoti įrankius turėsite naudoti @agent komandą.",
},
chat: {
title: "Pokalbis",
- description:
- "pateiks atsakymus remdamasis LLM bendrosiomis žiniomis ir rastu dokumentų kontekstu.
Norėdami naudoti įrankius, turėsite naudoti @agent komandą.",
},
query: {
title: "Užklausa",
- description:
- "pateiks atsakymus tik jei bus rastas dokumentų kontekstas.
Norėdami naudoti įrankius, turėsite naudoti @agent komandą.",
},
},
history: {
@@ -834,7 +831,6 @@ const TRANSLATIONS = {
source_count_other: "{{count}} nuorodos",
document: "Dokumentas",
similarity_match: "atitikimas",
- pause_tts_speech_message: "Sustabdyti garsinį žinutės skaitymą",
fork: "Atskirti (Fork)",
delete: "Ištrinti",
cancel: "Atšaukti",
@@ -866,7 +862,6 @@ const TRANSLATIONS = {
normal: "Normalus",
large: "Didelis",
tools: "Įrankiai",
- browse: "Naršyti",
text_size_label: "Teksto dydis",
select_model: "Pasirinkti modelį",
slash_commands: "Komandos su „/“",
@@ -1016,6 +1011,86 @@ const TRANSLATIONS = {
},
},
},
+ telegram: {
+ title: "Telegram robotas",
+ description:
+ "Prisijunkite savo „AnythingLLM“ instanciją prie „Telegram“, kad galėtumėte kalbėti su savo darbo vietomis iš bet kurio įrenginio.",
+ setup: {
+ step1: {
+ title: "1 žingsnis: Sukurkite savo Telegram botą",
+ description:
+ "Atidarykite @BotFather kanalą Telegram, atsiųskite `/newbot` į @BotFather, sekite instrukcijas ir kopijuokite API raktą.",
+ "open-botfather": "Atidarykite „BotFather“",
+ "instruction-1": "1. Atidarykite nuorodą arba nuskaitykite QR kodą",
+ "instruction-2":
+ "2. Siųkite /newbot adresą @BotFather",
+ "instruction-3":
+ "3. Pasirinkite pavadinimą ir vartotojo vardą savo botui.",
+ "instruction-4": "4. Paškopuokite gautą API žymiklį.",
+ },
+ step2: {
+ title: "2 etapas: Prisijunkite prie savo „bot“",
+ description:
+ "Įveskite API žymiklį, kurį gavote iš @BotFather, ir pasirinkite numatytą darbo vietą, kur jūsų bot galės kalbėti.",
+ "bot-token": "„Bot Token“",
+ "default-workspace": "Numatytasis darbo erdvė",
+ "no-workspace": "Nėra laisvų darbo vietų. Bus sukurta nauja.",
+ connecting: "Prisijungiam...",
+ "connect-bot": "„Connect Bot“",
+ },
+ security: {
+ title: "Išvardytos saugos nustatymai",
+ description:
+ "Papildomos saugumo užtikrinimui, konfigūruokite šias nustatymus @BotFather.",
+ "disable-groups": "– Prieš pridėdamas botą į grupes",
+ "disable-inline":
+ "– Užtikrinkite, kad „bot“ negali būti naudojamas paieškoje.",
+ "obscure-username":
+ "Naudokite neatsinaužytą „bot“ vardą, kad sumažintumėte jo aptikimo galimybes.",
+ },
+ "toast-enter-token": "Prašome įvesti boto žymę.",
+ "toast-connect-failed": "Nepavyko susieti robotą.",
+ },
+ connected: {
+ status: "Susijęs",
+ "status-disconnected":
+ "Nusijungtas – žetonas gali būti neregistruotas arba netinkamas",
+ "placeholder-token": "Įdiekite naują „bot“ žetoną…",
+ reconnect: "Vykdyti ryšį",
+ workspace: "Darbo zona",
+ "bot-link": "„Bot Link“",
+ "voice-response": "Garsas kaip atsakymas",
+ disconnecting: "Atsijungimas...",
+ disconnect: "Nutraukti ryšį",
+ "voice-text-only": "Tik tekstas",
+ "voice-mirror":
+ "Atspindžio funkcija (atsakymas balsu, kai vartotojas siunčia balsą)",
+ "voice-always":
+ "Visada naudokite balsą (siųsdami garsą su kiekvienu atsakymu)",
+ "toast-disconnect-failed": "Nepavyko atjungti robotą.",
+ "toast-reconnect-failed": "Nepavyko atkurti ryšį su botu.",
+ "toast-voice-failed": "Nepavyko atnaujinti balsinio režimo.",
+ "toast-approve-failed": "Nepavyko patvirtinti vartotojo.",
+ "toast-deny-failed": "Nepavyko užtikrinti vartotojo saugumo.",
+ "toast-revoke-failed": "Nepavyko atšalinti vartotojo.",
+ },
+ users: {
+ "pending-title": "Laikant patvirtinimo",
+ "pending-description":
+ "Naudotojai, laukiantys patvirtinimo. Palyginkite čia pateiktą kodą su tuo, kuris rodomas jų „Telegram“ pokalbyje.",
+ "approved-title": "Įsijungę vartotojai",
+ "approved-description":
+ "Naudotojai, kuriems suteikiama galimybė kalbėti su jūsų botu.",
+ user: "Naudotojas",
+ "pairing-code": "Kombinacijos kodas",
+ "no-pending": "Nėra atidėtų užklausų",
+ "no-approved": "Nėra patvirtintų vartotojų",
+ unknown: "Nenurodytas",
+ approve: "Aptinka",
+ deny: "Atsisakyti",
+ revoke: "Anuliuoti",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/lv/common.js b/frontend/src/locales/lv/common.js
index cf2dcb38..eda0f2ed 100644
--- a/frontend/src/locales/lv/common.js
+++ b/frontend/src/locales/lv/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Darba telpas nosaukums",
- user: "Lietotājs",
selection: "Modeļa izvēle",
saving: "Saglabā...",
save: "Saglabāt izmaiņas",
@@ -105,6 +104,10 @@ const TRANSLATIONS = {
"your-account": "Jūsu konts",
"import-item": "Importētā prece",
},
+ channels: "Kanāli",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -188,18 +191,12 @@ const TRANSLATIONS = {
title: "Sarunas režīms",
chat: {
title: "Saruna",
- description:
- 'sniedz atbildes, izmantojot LLM vispārīgo zināšanu un dokumenta kontekstu, kas ir pieejams.
Lai izmantotu rīkus, jums jāizmantojat komandu "@agent".',
},
query: {
title: "Vaicājums",
- description:
- 'sniedz atbildes tikai , ja dokumenta konteksts ir atrasts.
Lai izmantotu rīkus, jums būs jāizmanto komanda "@agent".',
},
automatic: {
title: "Automobiļs",
- description:
- 'automātiski izmantos rīkus, ja modelis un sniedzējs atbalsta vietējo rīku izmantošanu.
Ja vietējā rīku izmantošana netiek atbalstīta, jums būs jāizmantojas "@agent" komanda, lai izmantotu rīkus.',
},
},
history: {
@@ -840,7 +837,6 @@ const TRANSLATIONS = {
see_less: "Skatīt mazāk",
see_more: "Skatīt vairāk",
tools: "Rīki",
- browse: "Izpētiet",
text_size_label: "Teksta izmērs",
select_model: "Izvēlieties modeli",
sources: "Avotus",
@@ -853,8 +849,6 @@ const TRANSLATIONS = {
edit: "Rediģēt",
publish: "Publicēt",
stop_generating: "Atsauciet atbildes ģenerēšanu",
- pause_tts_speech_message:
- "Pārtrauciet TTS (teksta-izrunas) žēstā vēstījuma izrunu.",
slash_commands: "Īs termini komandās",
agent_skills: "Aģenta prasmes",
manage_agent_skills: "Iesaista aģenta prasmes",
@@ -1020,6 +1014,86 @@ const TRANSLATIONS = {
"Jūs nav piešķirts nevienai darba vietai.\nLūdzu, sazinieties ar savu administratoru, lai pieprasītu piekļuvi darba vietai.",
goToWorkspace: 'Pāriet uz darba vietu "{{workspace}}"',
},
+ telegram: {
+ title: "Telegram bot",
+ description:
+ "Iespējiet savu AnythingLLM instanci, lai varētu tikt savienots ar Telegram, un tāpēc varēsat runāt ar saviem darba grupām no jebkura ierīces.",
+ setup: {
+ step1: {
+ title: "1. darbība: Izveidot savu Telegram botu",
+ description:
+ "Atveriet `@BotFather` Telegramā, nosūtiet `/newbot` un ievietojiet to adresē @BotFather, sekojiet norādījumiem un kopējiet API atslēgu.",
+ "open-botfather": "Atvērt BotFather",
+ "instruction-1": "1. Atveriet saiti vai skenējiet QR kodu",
+ "instruction-2":
+ "2. Nosūtiet /newbot uz @BotFather",
+ "instruction-3":
+ "3. Izvēlieties nosaukumu un lietotājvārdu savam botam",
+ "instruction-4": "4. Kopējiet API atslēgu, ko saņemat",
+ },
+ step2: {
+ title: "2. darbība: Pievienojiet savu botu",
+ description:
+ "Ievietojiet API atslēgu, ko saņēsit no @BotFather, un izvēlieties nokārtotā darba telpu, kuras jūsu bots varēs veikt sazi.",
+ "bot-token": "Bots tokens",
+ "default-workspace": "Pamatojas darba videne",
+ "no-workspace": "Nav pieejamas darba vietas. Tiks izveidota jauna.",
+ connecting: "Savienojums...",
+ "connect-bot": "Saistītais bot",
+ },
+ security: {
+ title: "Ieteicamās drošības iestatījumi",
+ description:
+ "Lai nodrošinātu papildu drošību, konfigurējiet šos iestatījumus, izmantojot @BotFather.",
+ "disable-groups": "— Novērst, lai boti tiktu pievienoti grupām",
+ "disable-inline":
+ "— Novērst, lai bots tiktu izmantoti tiešajā meklēšanā.",
+ "obscure-username":
+ "Izmantojiet neparādu botu lietotāju vārdu, lai samazinātu atklājamo iespēju.",
+ },
+ "toast-enter-token": "Lūdzu, ievadiet bot tokenu.",
+ "toast-connect-failed": "Neizdevās pievienot botu.",
+ },
+ connected: {
+ status: "Saistīts",
+ "status-disconnected":
+ "Atvienots — tokens var būt nolaidēts vai nederīgs",
+ "placeholder-token": "Ievietojiet jaunu bot tokenu...",
+ reconnect: "Atjaunot sazi",
+ workspace: "Darba telpa",
+ "bot-link": "Bots saite",
+ "voice-response": "Balss atbildes",
+ disconnecting: "Atvienojot...",
+ disconnect: "Izslēgt",
+ "voice-text-only": "Tikai teksts",
+ "voice-mirror":
+ "Atspoguļošana (atbildēt ar balsi, kad lietotājs nosauc balsi)",
+ "voice-always":
+ "Vienmēr pievienojiet audio (sūtiet audio ar katru atbildi).",
+ "toast-disconnect-failed": "Neizdevās izslēgt botu.",
+ "toast-reconnect-failed": "Neizdevās atjaunot saikni ar botu.",
+ "toast-voice-failed": "Neizdevās atjaunināt balsī noteiktās režimas.",
+ "toast-approve-failed": "Nespēja apstiprināt lietotāju.",
+ "toast-deny-failed": "Nespēja atspējot lietotāju.",
+ "toast-revoke-failed": "Neizdevās atcelt lietotāja tiesības.",
+ },
+ users: {
+ "pending-title": "Atkarībā no apstākļiem",
+ "pending-description":
+ "Izmantotāji, kas gaida apstiprinājumu. Salīdziniet šeit norādīto koda numuru ar to, kas redzams viņu Telegram sarunā.",
+ "approved-title": "Atļautie lietotāji",
+ "approved-description":
+ "Izmantotāji, kuriem ir atļauts veikt saziņai ar jūsu botu.",
+ user: "Izmantotājs",
+ "pairing-code": "Kopējā koda numura kombinācija",
+ "no-pending": "Neizpildīti pieprasījumi",
+ "no-approved": "No apstiprinātiem lietotājiem",
+ unknown: "Nezināms",
+ approve: "Aptver",
+ deny: "Atbrīsties; atgrūst",
+ revoke: "Atcel",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/nl/common.js b/frontend/src/locales/nl/common.js
index dda428be..168947f4 100644
--- a/frontend/src/locales/nl/common.js
+++ b/frontend/src/locales/nl/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Werkruimten Naam",
- user: "Gebruiker",
selection: "Model Selectie",
saving: "Opslaan...",
save: "Wijzigingen opslaan",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Uw account",
"import-item": "Importeren",
},
+ channels: "Kanaal",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -181,18 +184,12 @@ const TRANSLATIONS = {
title: "Chatmodus",
chat: {
title: "Chat",
- description:
- "zal antwoorden geven met de algemene kennis van het LLM en de relevante context uit het document. U moet het `@agent`-commando gebruiken om tools te gebruiken.",
},
query: {
title: "Query",
- description:
- "zal antwoorden alleen geven, indien de context van het document wordt gevonden.
U moet het commando @agent gebruiken om tools te gebruiken.",
},
automatic: {
title: "Auto",
- description:
- "zal automatisch tools gebruiken als het model en de provider native tool-aanroepen ondersteunen.
Als native tooling niet wordt ondersteund, moet u het `@agent`-commando gebruiken om tools te gebruiken.",
},
},
history: {
@@ -744,7 +741,6 @@ const TRANSLATIONS = {
see_less: "Minder zien",
see_more: "Meer zien",
tools: "Gereedschap",
- browse: "Bladeren",
text_size_label: "Lettergrootte",
select_model: "Kies het model",
sources: "Bronnen",
@@ -757,7 +753,6 @@ const TRANSLATIONS = {
edit: "Bewerk",
publish: "Publiceren",
stop_generating: "Stoppen met het genereren van antwoorden",
- pause_tts_speech_message: "Pauzeer de spraak van de tekstberichten.",
slash_commands: "Korte commando's",
agent_skills: "Vaardigheden van agenten",
manage_agent_skills: "Beheer van de vaardigheden van de agent",
@@ -1020,6 +1015,88 @@ const TRANSLATIONS = {
"Je bent nog niet toegewezen aan een werkruimte.\nNeem contact op met je beheerder om toegang te vragen tot een werkruimte.",
goToWorkspace: 'Ga naar de werkruimte "{{workspace}}"',
},
+ telegram: {
+ title: "Telegram Bot",
+ description:
+ "Verbind uw AnythingLLM-instantie met Telegram, zodat u vanuit elk apparaat kunt communiceren met uw werkruimtes.",
+ setup: {
+ step1: {
+ title: "Stap 1: Maak je Telegram-bot",
+ description:
+ "Open het @BotFather-kanaal in Telegram, stuur `/newbot` naar @BotFather, volg de instructies en kopieer het API-token.",
+ "open-botfather": "Open BotFather",
+ "instruction-1": "1. Open het link of scan de QR-code",
+ "instruction-2":
+ "2. Stuur /newbot naar @BotFather",
+ "instruction-3": "3. Kies een naam en gebruikersnaam voor je bot",
+ "instruction-4": "4. Kopieer de API-token die je ontvangt",
+ },
+ step2: {
+ title: "Stap 2: Verbind uw bot",
+ description:
+ "Plak de API-token die je van @BotFather hebt ontvangen en selecteer een standaard werkruimte voor je bot om mee te communiceren.",
+ "bot-token": "Bot-token",
+ "default-workspace": "Standaard werkruimte",
+ "no-workspace":
+ "Er zijn geen beschikbare werkplekken. Een nieuwe zal worden aangemaakt.",
+ connecting: "Verbinding wordt gemaakt...",
+ "connect-bot": "Connect Bot",
+ },
+ security: {
+ title: "Aanbevolen beveiligingsinstellingen",
+ description:
+ "Voor extra beveiliging, configureer deze instellingen via @BotFather.",
+ "disable-groups": "— Voorkom het toevoegen van bots aan groepen",
+ "disable-inline":
+ "— Voorkom dat de bot wordt gebruikt in inline zoekopdrachten",
+ "obscure-username":
+ "Gebruik een bot-username dat niet direct herkenbaar is, om de vindbaarheid te verminderen.",
+ },
+ "toast-enter-token": "Voer alstublieft een bot-token in.",
+ "toast-connect-failed": "Verbinding met de bot is mislukt.",
+ },
+ connected: {
+ status: "Verbonden",
+ "status-disconnected":
+ "Niet verbonden – het token kan verlopen zijn of ongeldig",
+ "placeholder-token": "Plak het nieuwe bot-token...",
+ reconnect: "Herstellen van de verbinding",
+ workspace: "Werkplek",
+ "bot-link": "Bot-link",
+ "voice-response": "Spraakherkenning",
+ disconnecting: "Verbinding verbreken...",
+ disconnect: "Aansluiting verbreiden",
+ "voice-text-only": "Alleen tekst",
+ "voice-mirror":
+ "Spiegel (antwoord met spraak wanneer de gebruiker spraak verzendt)",
+ "voice-always":
+ "Zorg ervoor dat er altijd een audio-opname (een geluidsfragment) bij de reactie wordt toegevoegd.",
+ "toast-disconnect-failed":
+ "Het was niet mogelijk om de robot los te koppelen.",
+ "toast-reconnect-failed": "Fout bij het opnieuw verbinden van de bot.",
+ "toast-voice-failed": "Niet mogelijk om de spraakmodus bij te werken.",
+ "toast-approve-failed": "Fout bij goedkeuren van gebruiker.",
+ "toast-deny-failed": "Niet in staat om gebruiker te weigeren.",
+ "toast-revoke-failed":
+ "Fout bij het intrekken van het gebruikersaccount.",
+ },
+ users: {
+ "pending-title": "Afhankelijk van goedkeuring",
+ "pending-description":
+ "Gebruikers die nog geverifieerd moeten worden. Vergelijk de code die hier wordt getoond met de code die in hun Telegram-chat wordt weergegeven.",
+ "approved-title": "Goedgekeurde gebruikers",
+ "approved-description":
+ "Gebruikers die zijn goedgekeurd om met uw bot te communiceren.",
+ user: "Gebruiker",
+ "pairing-code": "Code voor het koppelen",
+ "no-pending": "Er zijn geen lopende verzoeken.",
+ "no-approved": "Geen goedgekeurde gebruikers",
+ unknown: "Onbekend",
+ approve: "Goedkeuren",
+ deny: "Afgewijzen",
+ revoke: "Intrekken",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/pl/common.js b/frontend/src/locales/pl/common.js
index 7b7cac9f..fcc60e6b 100644
--- a/frontend/src/locales/pl/common.js
+++ b/frontend/src/locales/pl/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Nazwa obszaru roboczego",
- user: "Użytkownik",
selection: "Wybór modelu",
saving: "Zapisywanie...",
save: "Zapisz zmiany",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Twój profil",
"import-item": "Importuj element",
},
+ channels: "Kanały",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -188,18 +191,12 @@ const TRANSLATIONS = {
title: "Tryb czatu",
chat: {
title: "Czat",
- description:
- "zapewni odpowiedzi, wykorzystując ogólną wiedzę LLM oraz kontekst dokumentu, w którym ta wiedza znajduje się.
Będziesz musiał użyć komendy `@agent` w celu korzystania z narzędzi.",
},
query: {
title: "Zapytanie (wyszukiwanie)",
- description:
- "będzie dostarczać odpowiedzi tylko, jeśli zostanie zidentyfikowany kontekst dokumentu.
Będziesz musiał użyć komendy `@agent` w celu korzystania z narzędzi.",
},
automatic: {
title: "Samochód",
- description:
- "automatycznie będzie wykorzystywał narzędzia, jeśli model i dostawca obsługują natywne wywoływanie narzędzi. Jeśli natywne narzędzia nie są obsługiwane, konieczne będzie użycie polecenia `@agent` w celu wykorzystania narzędzi.",
},
},
history: {
@@ -837,7 +834,6 @@ const TRANSLATIONS = {
see_less: "Zobacz mniej",
see_more: "Zobacz więcej",
tools: "Narzędzia",
- browse: "Przeglądaj",
text_size_label: "Rozmiar czcionki",
select_model: "Wybierz model",
sources: "Źródła",
@@ -850,7 +846,6 @@ const TRANSLATIONS = {
edit: "Edytuj",
publish: "Opublikować",
stop_generating: "Przestań generować odpowiedź",
- pause_tts_speech_message: "Wstrzymać odtwarzanie mowy z wiadomości",
slash_commands: "Polecenia skrótowe",
agent_skills: "Umiejętności agenta",
manage_agent_skills: "Zarządzanie umiejętnościami agentów",
@@ -1016,6 +1011,87 @@ const TRANSLATIONS = {
"Nie jesteś przypisany do żadnego obszaru roboczego.\nSkontaktuj się z administratorem, aby poprosić o dostęp do obszaru roboczego.",
goToWorkspace: 'Przejdź do obszaru roboczego "{{workspace}}"',
},
+ telegram: {
+ title: "Bot na Telegramie",
+ description:
+ "Połącz swoją instancję AnythingLLM z Telegramem, aby móc rozmawiać z przestrzeniami roboczymi z dowolnego urządzenia.",
+ setup: {
+ step1: {
+ title: "Krok 1: Utwórz swojego bota w Telegramie",
+ description:
+ "Otwórz aplikację @BotFather w Telegramie, wyślij wiadomość /newbot do @BotFather, postępuj zgodnie z instrukcjami i skopiuj token API.",
+ "open-botfather": "Otwórz BotFather",
+ "instruction-1": "1. Otwórz link lub zeskanuj kod QR",
+ "instruction-2":
+ "2. Wyślij /newbot na adres @BotFather",
+ "instruction-3":
+ "3. Wybierz nazwę i nazwę użytkownika dla swojego robota.",
+ "instruction-4": "4. Skopiuj token API, który otrzymasz.",
+ },
+ step2: {
+ title: "Krok 2: Połącz swojego robota",
+ description:
+ "Wklej token API, który otrzymałeś od @BotFather, i wybierz domyślny przestrzeń roboczą, z której Twój bot będzie mógł komunikować się.",
+ "bot-token": "Token Bot",
+ "default-workspace": "Domyślne miejsce pracy",
+ "no-workspace":
+ "Brak dostępnych miejsc pracy. Nowe zostanie utworzone.",
+ connecting: "Połączenie...",
+ "connect-bot": "Bot łączący",
+ },
+ security: {
+ title: "Zalecane ustawienia bezpieczeństwa",
+ description:
+ "W celu zwiększenia bezpieczeństwa, skonfiguruj te ustawienia w kanale @BotFather.",
+ "disable-groups": "— Zapobiegaj dodawaniu botów do grup",
+ "disable-inline":
+ "— Zapobieg użyciu robota w wyszukiwaniach w czasie rzeczywistym.",
+ "obscure-username":
+ "Użyj nietypowej nazwy użytkownika dla bota, aby zmniejszyć jego widoczność.",
+ },
+ "toast-enter-token": "Prosimy o wprowadzenie tokena dla bota.",
+ "toast-connect-failed": "Nie udało się nawiązać połączenia z botem.",
+ },
+ connected: {
+ status: "Połączony",
+ "status-disconnected":
+ "Brak połączenia – token może być nieprawidłowy lub wygasł",
+ "placeholder-token": "Wklej nowy token dla bota...",
+ reconnect: "Ponowne połączenie",
+ workspace: "Przestrzeń robocza",
+ "bot-link": "Link do bota",
+ "voice-response": "Reakcja na głos",
+ disconnecting: "Odłączanie...",
+ disconnect: "Odłączyć",
+ "voice-text-only": "Tylko tekst",
+ "voice-mirror":
+ "Odbiór (odpowiedź za pomocą głosu, gdy użytkownik wysyła głos)",
+ "voice-always":
+ "Zawsze dołączaj nagranie (wysyłaj dźwięk wraz z każdą odpowiedzią)",
+ "toast-disconnect-failed": "Nie udało się odłączyć bota.",
+ "toast-reconnect-failed": "Nie udało się nawiązać połączenia z botem.",
+ "toast-voice-failed": "Nie udało się zaktualizować trybu głosu.",
+ "toast-approve-failed": "Nie udało się zatwierdzić użytkownika.",
+ "toast-deny-failed": "Nie udało się odrzucić żądania użytkownika.",
+ "toast-revoke-failed": "Nie udało się odwołać konta użytkownika.",
+ },
+ users: {
+ "pending-title": "Czekając na zatwierdzenie",
+ "pending-description":
+ "Użytkownicy, którzy czekają na weryfikację. Dopasuj kod parowania, który znajduje się tutaj, z tym, który widnieje w ich rozmowie na Telegramie.",
+ "approved-title": "Użytkownicy, którym przyznano uprawnienia",
+ "approved-description":
+ "Użytkownicy, którzy zostali zatwierdzeni do rozmowy z Twoim botem.",
+ user: "Użytkownik",
+ "pairing-code": "Kod dopasowania",
+ "no-pending": "Brak oczekujących żądań",
+ "no-approved": "Brak zatwierdzonych użytkowników",
+ unknown: "Nieznany",
+ approve: "Zaakceptować",
+ deny: "Odrzucać",
+ revoke: "Odstrzegać",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js
index 81e706c4..70040d66 100644
--- a/frontend/src/locales/pt_BR/common.js
+++ b/frontend/src/locales/pt_BR/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Nome do Workspace",
- user: "Usuário",
selection: "Seleção de Modelo",
saving: "Salvando...",
save: "Salvar alterações",
@@ -105,6 +104,10 @@ const TRANSLATIONS = {
"your-account": "Sua Conta",
"import-item": "Importar Item",
},
+ channels: "Canais",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -188,18 +191,12 @@ const TRANSLATIONS = {
title: "Modo de Chat",
chat: {
title: "Chat",
- description:
- 'fornecerá respostas com base no conhecimento geral do LLM e no contexto do documento encontrado.
Você precisará usar o comando "@agent" para utilizar as ferramentas.',
},
query: {
title: "Consulta",
- description:
- 'fornecerá respostas apenas caso o contexto do documento seja encontrado.
Você precisará usar o comando "@agent" para utilizar as ferramentas.',
},
automatic: {
title: "Automóvel",
- description:
- 'utilizará automaticamente as ferramentas, se o modelo e o provedor suportarem a chamada nativa de ferramentas. Se a chamada nativa de ferramentas não for suportada, você precisará usar o comando "@agent" para utilizar as ferramentas.',
},
},
history: {
@@ -821,7 +818,6 @@ const TRANSLATIONS = {
see_less: "Ver menos",
see_more: "Ver mais",
tools: "Ferramentas",
- browse: "Navegar",
text_size_label: "Tamanho do texto",
select_model: "Selecione o modelo",
sources: "Fontes",
@@ -834,7 +830,6 @@ const TRANSLATIONS = {
edit: "Editar",
publish: "Publicar",
stop_generating: "Pare de gerar respostas",
- pause_tts_speech_message: "Pausar a leitura de voz da mensagem",
slash_commands: "Comandos Rápidos",
agent_skills: "Habilidades do Agente",
manage_agent_skills: "Gerenciar as habilidades dos agentes",
@@ -992,6 +987,86 @@ const TRANSLATIONS = {
"Você ainda não está atribuído a nenhum espaço de trabalho.\nEntre em contato com seu administrador para solicitar acesso a um espaço de trabalho.",
goToWorkspace: 'Ir para o espaço de trabalho "{{workspace}}"',
},
+ telegram: {
+ title: "Bot do Telegram",
+ description:
+ "Conecte sua instância do AnythingLLM ao Telegram para que possa conversar com seus espaços de trabalho a partir de qualquer dispositivo.",
+ setup: {
+ step1: {
+ title: "Passo 1: Crie seu bot do Telegram",
+ description:
+ "Abra o @BotFather no Telegram, envie /newbot para @BotFather, siga as instruções e copie o token da API.",
+ "open-botfather": "Iniciar o BotFather",
+ "instruction-1": "1. Abra o link ou escaneie o código QR.",
+ "instruction-2":
+ "2. Envie /newbot para @BotFather",
+ "instruction-3":
+ "3. Escolha um nome e um nome de usuário para o seu bot.",
+ "instruction-4": "4. Copie o token da API que você receber.",
+ },
+ step2: {
+ title: "Passo 2: Conecte seu bot",
+ description:
+ "Cole o token da API que recebeu do @BotFather e selecione um espaço de trabalho padrão para que seu bot possa conversar.",
+ "bot-token": "Token do Bot",
+ "default-workspace": "Espaço de Trabalho Padrão",
+ "no-workspace":
+ "Não há espaços de trabalho disponíveis. Um novo será criado.",
+ connecting: "Conectando...",
+ "connect-bot": "Bot de Conexão",
+ },
+ security: {
+ title: "Configurações de segurança recomendadas",
+ description:
+ "Para maior segurança, configure estas opções no @BotFather.",
+ "disable-groups": "— Impedir a adição de bots a grupos",
+ "disable-inline": "— Impedir que o bot seja usado na pesquisa inline.",
+ "obscure-username":
+ "Utilize um nome de usuário de bot menos óbvio para reduzir a sua visibilidade.",
+ },
+ "toast-enter-token": "Por favor, insira um token de bot.",
+ "toast-connect-failed": "Falhou a conexão com o bot.",
+ },
+ connected: {
+ status: "Conectado",
+ "status-disconnected":
+ "Desconectado — o token pode ter expirado ou ser inválido",
+ "placeholder-token": "Cole o novo token do bot...",
+ reconnect: "Reconectar",
+ workspace: "Espaço de trabalho",
+ "bot-link": "Link do bot",
+ "voice-response": "Resposta por voz",
+ disconnecting: "Desconectando...",
+ disconnect: "Desconectar",
+ "voice-text-only": "Apenas texto",
+ "voice-mirror":
+ "Espelho (responder com voz quando o usuário enviar uma mensagem de voz)",
+ "voice-always":
+ "Sempre inclua uma gravação de áudio (envie um áudio com cada resposta).",
+ "toast-disconnect-failed": "Falhou ao desconectar o bot.",
+ "toast-reconnect-failed": "Falha ao tentar reconectar o bot.",
+ "toast-voice-failed": "Falhou ao atualizar o modo de voz.",
+ "toast-approve-failed": "Falhou ao aprovar o usuário.",
+ "toast-deny-failed": "Não foi possível negar o acesso ao usuário.",
+ "toast-revoke-failed": "Falhou ao revogar o acesso do usuário.",
+ },
+ users: {
+ "pending-title": "Aguardando Aprovação",
+ "pending-description":
+ "Usuários aguardando a verificação. Compare o código de pareamento exibido aqui com o que aparece em seu chat do Telegram.",
+ "approved-title": "Usuários Aprovados",
+ "approved-description":
+ "Usuários que foram aprovados para conversar com o seu bot.",
+ user: "Usuário",
+ "pairing-code": "Código de emparelhamento",
+ "no-pending": "Não há solicitações pendentes.",
+ "no-approved": "Sem usuários autorizados",
+ unknown: "Desconhecido",
+ approve: "Aprovar",
+ deny: "Negar",
+ revoke: "Anular",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/ro/common.js b/frontend/src/locales/ro/common.js
index 260764da..9f727891 100644
--- a/frontend/src/locales/ro/common.js
+++ b/frontend/src/locales/ro/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Numele spațiilor de lucru",
- user: "Utilizator",
selection: "Selecția modelului",
saving: "Se salvează...",
save: "Salvează modificările",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Contul dumneavoastră",
"import-item": "Importați articolul",
},
+ channels: "Canale",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -190,18 +193,12 @@ const TRANSLATIONS = {
title: "Mod chat",
chat: {
title: "Chat",
- description:
- 'va oferi răspunsuri folosind cunoștințele generale ale modelului LLM și contextul documentului respectiv.
Va trebui să utilizați comanda "@agent" pentru a utiliza instrumentele.',
},
query: {
title: "Interogare",
- description:
- 'vor oferi răspunsuri **doar** dacă contextul documentului este identificat. Veți avea nevoie să utilizați comanda "@agent" pentru a utiliza instrumentele.',
},
automatic: {
title: "Mașină",
- description:
- 'va utiliza automat instrumentele, dacă modelul și furnizorul suportă apelarea nativă a instrumentelor.
Dacă apelarea nativă a instrumentelor nu este suportată, veți avea nevoie să utilizați comanda "@agent" pentru a utiliza instrumentele.',
},
},
history: {
@@ -546,7 +543,6 @@ const TRANSLATIONS = {
see_less: "Vezi mai puțin",
see_more: "Vezi mai multe",
tools: "Unelte",
- browse: "Navigați",
text_size_label: "Dimensiunea textului",
select_model: "Selectați modelul",
sources: "Surse",
@@ -559,8 +555,6 @@ const TRANSLATIONS = {
edit: "Editează",
publish: "Publica",
stop_generating: "Opriți generarea răspunsului",
- pause_tts_speech_message:
- "Pauză în redarea vocii prin Text-to-Speech (TTS) a mesajului.",
slash_commands: "Comenzi scurte",
agent_skills: "Abilități ale agentului",
manage_agent_skills: "Gestionarea competențelor agenților",
@@ -1021,6 +1015,88 @@ const TRANSLATIONS = {
"Momentan nu te-ai atribuit la niciun spațiu de lucru.\nContactează-ți administratorul pentru a solicita acces la un spațiu de lucru.",
goToWorkspace: 'Mai departe la spațiul de lucru "{{workspace}}"',
},
+ telegram: {
+ title: "Bot pentru Telegram",
+ description:
+ "Conectați instanța dumneavoastră AnythingLLM cu Telegram, astfel încât să puteți interacționa cu spațiile de lucru de pe orice dispozitiv.",
+ setup: {
+ step1: {
+ title: "Pasul 1: Creați botul dumneavoastră Telegram",
+ description:
+ "Deschide chatul cu @BotFather pe Telegram, trimite mesajul `/newbot` către @BotFather, urmează instrucțiunile și copiază token-ul API.",
+ "open-botfather": "Deschide aplicația BotFather",
+ "instruction-1": "1. Deschideți link-ul sau scanați codul QR",
+ "instruction-2":
+ "2. Trimite /newbot către @BotFather",
+ "instruction-3":
+ "3. Alege un nume și un nume de utilizator pentru botul tău.",
+ "instruction-4": "4. Copiați token-ul API pe care îl primiți.",
+ },
+ step2: {
+ title: "Pasul 2: Conectați-vă bot-ul",
+ description:
+ "Lipește token-ul API pe care l-ați primit de la @BotFather și selectați un spațiu de lucru implicit pentru ca botul dumneavoastră să poată interacționa.",
+ "bot-token": "Token Bot",
+ "default-workspace": "Spațiu de lucru implicit",
+ "no-workspace":
+ "Nu există spații de lucru disponibile. Va fi creat unul nou.",
+ connecting: "Conectare...",
+ "connect-bot": "Conectare automată",
+ },
+ security: {
+ title: "Recomandări privind setările de securitate",
+ description:
+ "Pentru o securitate suplimentară, configurați aceste setări în contul @BotFather.",
+ "disable-groups": "— Preveniți adăugarea de bot-uri în grupuri",
+ "disable-inline":
+ "— Previne utilizarea bot-urilor în căutările directe",
+ "obscure-username":
+ "Utilizați un nume de utilizator pentru bot, care nu este evident, pentru a reduce vizibilitatea acestuia.",
+ },
+ "toast-enter-token": "Vă rugăm să introduceți un token pentru bot.",
+ "toast-connect-failed": "Nu a reușit să se conecteze bot-ul.",
+ },
+ connected: {
+ status: "Conectat",
+ "status-disconnected":
+ "Deconectat – token-ul poate fi expirat sau invalid",
+ "placeholder-token": "Creați un nou token pentru bot...",
+ reconnect: "Restabilește conexiunea",
+ workspace: "Spațiu de lucru",
+ "bot-link": "Link către bot",
+ "voice-response": "Răspuns vocal",
+ disconnecting: "Deconectare...",
+ disconnect: "Dezactivează",
+ "voice-text-only": "Doar text",
+ "voice-mirror":
+ "Reflectare (răspunde cu voce atunci când utilizatorul trimite înregistrare audio)",
+ "voice-always":
+ "Asigurați-vă întotdeauna că includeți un mesaj audio (trimiteți înregistrarea audio împreună cu fiecare răspuns).",
+ "toast-disconnect-failed": "Nu s-a reușit deconectarea bot-ului.",
+ "toast-reconnect-failed": "Nu a reușit să se reconecteze.",
+ "toast-voice-failed": "Nu a reușit să actualizeze modul de voce.",
+ "toast-approve-failed": "Nu a fost posibilă aprobarea utilizatorului.",
+ "toast-deny-failed": "Nu a reușit să respingă cererea utilizatorului.",
+ "toast-revoke-failed":
+ "Nu a fost posibil să se anuleze contul utilizatorului.",
+ },
+ users: {
+ "pending-title": "Așteptare aprobare",
+ "pending-description":
+ "Utilizatorii care așteaptă să fie verificați. Potrivirea codului de asociere afișat aici cu cel afișat în chat-ul lor de pe Telegram.",
+ "approved-title": "Utilizatori autorizați",
+ "approved-description":
+ "Utilizatorii care au fost autorizați să interacționeze cu botul dumneavoastră.",
+ user: "Utilizator",
+ "pairing-code": "Cod de asociere",
+ "no-pending": "Nu există cereri în așteptare.",
+ "no-approved": "Nu există utilizatori autorizați.",
+ unknown: "Necunoscut",
+ approve: "Aprobă",
+ deny: "Negarea",
+ revoke: "Anula",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js
index 5b771897..db3164ca 100644
--- a/frontend/src/locales/ru/common.js
+++ b/frontend/src/locales/ru/common.js
@@ -51,7 +51,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Имя рабочих пространств",
- user: "Пользователь",
selection: "Выбор модели",
saving: "Сохранение...",
save: "Сохранить изменения",
@@ -105,6 +104,10 @@ const TRANSLATIONS = {
"your-account": "Ваш аккаунт",
"import-item": "Импорт товара",
},
+ channels: "Каналы",
+ "available-channels": {
+ telegram: "Телеграм",
+ },
},
login: {
"multi-user": {
@@ -181,18 +184,12 @@ const TRANSLATIONS = {
title: "Режим чата",
chat: {
title: "Чат",
- description:
- "предоставит ответы, используя общие знания, содержащиеся в LLM, и контекст документа, который был предоставлен.
Для использования инструментов необходимо использовать команду @agent.",
},
query: {
title: "Запрос",
- description:
- "предоставит ответы только в том случае, если будет найден контекст документа.Для использования инструментов необходимо использовать команду @agent.",
},
automatic: {
title: "Авто",
- description:
- "автоматически будет использовать инструменты, если модель и поставщик поддерживают вызов инструментов.
Если вызов инструментов не поддерживается, вам потребуется использовать команду `@agent` для использования инструментов.",
},
},
history: {
@@ -747,7 +744,6 @@ const TRANSLATIONS = {
see_less: "Показать меньше",
see_more: "Узнать больше",
tools: "Инструменты",
- browse: "Просматривать",
text_size_label: "Размер текста",
select_model: "Выберите модель",
sources: "Источники",
@@ -760,8 +756,6 @@ const TRANSLATIONS = {
edit: "Редактировать",
publish: "Опубликовать",
stop_generating: "Прекратите генерацию ответа",
- pause_tts_speech_message:
- "Приостановить чтение текста с помощью синтезатора речи.",
slash_commands: "Команды, введенные сокращенной формой",
agent_skills: "Навыки агента",
manage_agent_skills: "Управление навыками агентов",
@@ -1027,6 +1021,86 @@ const TRANSLATIONS = {
"Вы не назначены ни к одной рабочей области.\nСвяжитесь с администратором, чтобы запросить доступ к рабочей области.",
goToWorkspace: 'Перейти к рабочей области "{{workspace}}"',
},
+ telegram: {
+ title: "Бот для Telegram",
+ description:
+ "Подключите свою инстанцию AnythingLLM к Telegram, чтобы вы могли общаться со своими рабочими пространствами с любого устройства.",
+ setup: {
+ step1: {
+ title: "Шаг 1: Создайте своего Telegram-бота.",
+ description:
+ "Откройте чат с @BotFather в Telegram, отправьте команду `/newbot` в чат с @BotFather, следуйте инструкциям и скопируйте API-токен.",
+ "open-botfather": "Запустить BotFather",
+ "instruction-1": "1. Откройте ссылку или отсканируйте QR-код",
+ "instruction-2":
+ "2. Отправьте /newbot на адрес @BotFather",
+ "instruction-3": "3. Выберите имя и имя пользователя для вашего бота.",
+ "instruction-4": "4. Скопируйте API-токен, который вы получили.",
+ },
+ step2: {
+ title: "Шаг 2: Подключите своего бота",
+ description:
+ "Вставьте API-токен, который вы получили от @BotFather, и выберите основной рабочий стол для вашего бота, чтобы он мог общаться.",
+ "bot-token": "Токен бота",
+ "default-workspace": "Основной рабочий стол",
+ "no-workspace": "Недоступны рабочие места. Будет создано новое.",
+ connecting: "Устанавливается соединение...",
+ "connect-bot": "Bot Connect",
+ },
+ security: {
+ title: "Рекомендуемые настройки безопасности",
+ description:
+ "Для дополнительной безопасности, настройте эти параметры в @BotFather.",
+ "disable-groups": "— Предотвратить добавление ботов в группы",
+ "disable-inline":
+ "— Предотвратить использование бота в поиске по запросу",
+ "obscure-username":
+ "Используйте не очевидное имя пользователя для бота, чтобы уменьшить его видимость.",
+ },
+ "toast-enter-token": "Пожалуйста, введите токен для бота.",
+ "toast-connect-failed": "Не удалось установить соединение с ботом.",
+ },
+ connected: {
+ status: "Соединенный",
+ "status-disconnected":
+ "Отключено – токен может быть просроченным или недействительным",
+ "placeholder-token": "Вставьте новый токен для бота...",
+ reconnect: "Возобновить",
+ workspace: "Рабочее пространство",
+ "bot-link": "Ссылка на бота",
+ "voice-response": "Ответ голосом",
+ disconnecting: "Отключение...",
+ disconnect: "Отключить",
+ "voice-text-only": "Текст только",
+ "voice-mirror":
+ "Зеркало (отвечать голосом, когда пользователь отправляет голосовое сообщение)",
+ "voice-always":
+ "Всегда добавляйте голосовое сообщение (отправляйте аудио вместе с каждым ответом).",
+ "toast-disconnect-failed": "Не удалось отключить бота.",
+ "toast-reconnect-failed": "Не удалось восстановить соединение с ботом.",
+ "toast-voice-failed": "Не удалось обновить режим голосового управления.",
+ "toast-approve-failed":
+ "Не удалось подтвердить учетную запись пользователя.",
+ "toast-deny-failed": "Не удалось отклонить запрос пользователя.",
+ "toast-revoke-failed": "Не удалось отменить действия пользователя.",
+ },
+ users: {
+ "pending-title": "Ожидается утверждение",
+ "pending-description":
+ "Пользователи, ожидающие подтверждения. Сравните код, указанный здесь, с кодом, отображаемым в их чате в Telegram.",
+ "approved-title": "Утвержденные пользователи",
+ "approved-description":
+ "Пользователи, которым разрешено общаться с вашим ботом.",
+ user: "Пользователь",
+ "pairing-code": "Код сопоставления",
+ "no-pending": "Отсутствуют незавершенные запросы.",
+ "no-approved": "Нет зарегистрированных пользователей",
+ unknown: "Неизвестно",
+ approve: "Одобрить",
+ deny: "Отрицать",
+ revoke: "Отменить",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/tr/common.js b/frontend/src/locales/tr/common.js
index 60d6688f..54f650ae 100644
--- a/frontend/src/locales/tr/common.js
+++ b/frontend/src/locales/tr/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Çalışma Alanları Adı",
- user: "Kullanıcı",
selection: "Model Seçimi",
saving: "Kaydediliyor...",
save: "Değişiklikleri Kaydet",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Hesabınız",
"import-item": "İthal Edilen Ürün",
},
+ channels: "Kanalalar",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -181,18 +184,12 @@ const TRANSLATIONS = {
title: "Sohbet Modu",
chat: {
title: "Sohbet",
- description:
- "LLM'nin genel bilgisi ve bulunan doküman bağlamıyla cevaplar sunacaktır. Araçları kullanmak için @agent komutunu kullanmanız gerekecektir.",
},
query: {
title: "Sorgu",
- description:
- "yalnızca doküman bağlamı bulunursa yanıtlar sağlayacaktır.İhtiyaç duyacağınız araçları kullanmak için @agent komutunu kullanmanız gerekecektir.",
},
automatic: {
title: "Oto",
- description:
- "
Varsa, model ve sağlayıcı tarafından desteklenen yerel araçları otomatik olarak kullanacaktır. Yerel araç kullanımı desteklenmiyorsa, araçları kullanmak için @agent komutunu kullanmanız gerekecektir.",
},
},
history: {
@@ -742,7 +739,6 @@ const TRANSLATIONS = {
see_less: "Daha az",
see_more: "Daha Fazla",
tools: "Araçlar",
- browse: "Gezin",
text_size_label: "Metin Boyutu",
select_model: "Model Seçimi",
sources: "Kaynaklar",
@@ -755,7 +751,6 @@ const TRANSLATIONS = {
edit: "Düzenle",
publish: "Yayınla",
stop_generating: "Yanıt üretmeyi durdurun",
- pause_tts_speech_message: "Mesajın metin okuma (TTS) özelliğini durdur",
slash_commands: "Komut Satırı Komutları",
agent_skills: "Ajansın Yetenekleri",
manage_agent_skills: "Temsilcinin becerilerini yönetin",
@@ -1013,6 +1008,85 @@ const TRANSLATIONS = {
"Şu anda hiçbir çalışma alanına atanmamışsınız.\nBir çalışma alanına erişmek için yöneticinize başvurun.",
goToWorkspace: 'Çalışma alanına git "{{workspace}}"',
},
+ telegram: {
+ title: "Telegram Bot'u",
+ description:
+ "AnythingLLM örneğinizi Telegram ile bağlantılandırarak, herhangi bir cihazdan çalışma alanlarınızla sohbet edebilmelisiniz.",
+ setup: {
+ step1: {
+ title: "1. Adım: Telegram botunuzu oluşturun",
+ description:
+ "Telegram uygulamasında @BotFather'ı açın, \"/newbot\" komutunu @BotFather'e gönderin, talimatları izleyin ve API anahtarını kopyalayın.",
+ "open-botfather": "BotFather'ı aç",
+ "instruction-1": "1. Bağlantıyı açın veya QR kodunu tarayın",
+ "instruction-2":
+ "2. /newbot adresine @BotFather'e gönderin.",
+ "instruction-3": "3. Botunuz için bir isim ve kullanıcı adı seçin",
+ "instruction-4": "4. Alınan API token'ı kopyalayın",
+ },
+ step2: {
+ title: "Adım 2: Botunuzu bağlayın",
+ description:
+ "Aldığınız API token'ı (@BotFather) kopyalayın ve botunuzun iletişim kuracağı varsayılan çalışma alanını seçin.",
+ "bot-token": "Bot Token",
+ "default-workspace": "Varsayılan Çalışma Alanı",
+ "no-workspace":
+ "Mevcut çalışma alanları bulunmamaktadır. Yeni bir çalışma alanı oluşturulacaktır.",
+ connecting: "Bağlantı kuruluyor...",
+ "connect-bot": "Bağlantı Botu",
+ },
+ security: {
+ title: "Önerilen Güvenlik Ayarları",
+ description:
+ "Ek güvenlik için, bu ayarları @BotFather üzerinden yapılandırın.",
+ "disable-groups": "— Gruplara bot eklenmesini engelleme",
+ "disable-inline":
+ "— Bot'un, arama çubuklarında kullanılmasını engellemek",
+ "obscure-username":
+ "Daha az bilinen bir bot kullanıcı adı kullanarak görünürlüğünü azaltın.",
+ },
+ "toast-enter-token": "Lütfen bir bot belirteci girin.",
+ "toast-connect-failed": "Bot ile bağlantı kurulamadı.",
+ },
+ connected: {
+ status: "Bağlı",
+ "status-disconnected":
+ "Bağlantı kesildi — belirteç geçersiz veya süresi dolmuş olabilir",
+ "placeholder-token": "Yeni bot token'ı yapıştırın...",
+ reconnect: "Yeniden bağlantı kur",
+ workspace: "Çalışma alanı",
+ "bot-link": "Bot bağlantısı",
+ "voice-response": "Sesle etkileşim",
+ disconnecting: "Bağlantıyı kesiyorum...",
+ disconnect: "Bağlantıyı kes",
+ "voice-text-only": "Sadece metin",
+ "voice-mirror":
+ "Sesli yanıt (kullanıcı ses gönderdiğinde, sesli yanıtla cevaplayın)",
+ "voice-always": "Her yanıtla birlikte sesli (sesli yanıt gönderme)",
+ "toast-disconnect-failed": "Bot'u ayırmada başarısız.",
+ "toast-reconnect-failed": "Bot yeniden bağlantı kuramadı.",
+ "toast-voice-failed": "Ses modunu güncelleme başarısız oldu.",
+ "toast-approve-failed": "Kullanıcıın onaylanması başarısız oldu.",
+ "toast-deny-failed": "Kullanıcıyı reddetmeyi başaramadı.",
+ "toast-revoke-failed": "Kullanıcıyı silme işlemi başarısız oldu.",
+ },
+ users: {
+ "pending-title": "Onay Bekliyor",
+ "pending-description":
+ "Doğrulama işlemi bekleyen kullanıcılar. Burada gösterilen eşleştirme kodunu, Telegram sohbetlerinde görüntülenen kodla karşılaştırın.",
+ "approved-title": "Onaylanmış Kullanıcılar",
+ "approved-description":
+ "Botunuzla sohbet etmeye yetkili olan kullanıcılar.",
+ user: "Kullanıcı",
+ "pairing-code": "Eşleştirme Kodu",
+ "no-pending": "Henüz tamamlanmamış herhangi bir istek bulunmamaktadır.",
+ "no-approved": "Onaylanmış kullanıcı bulunmamaktadır",
+ unknown: "Bilinmiyor",
+ approve: "Onayla",
+ deny: "İnkar",
+ revoke: "İptal et",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/vn/common.js b/frontend/src/locales/vn/common.js
index ac4c29b3..bd71e6ae 100644
--- a/frontend/src/locales/vn/common.js
+++ b/frontend/src/locales/vn/common.js
@@ -52,7 +52,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "Tên không gian làm việc",
- user: "Người dùng",
selection: "Lựa chọn mô hình",
saving: "Đang lưu...",
save: "Lưu thay đổi",
@@ -106,6 +105,10 @@ const TRANSLATIONS = {
"your-account": "Tài khoản của bạn",
"import-item": "Nhập hàng",
},
+ channels: "Kênh",
+ "available-channels": {
+ telegram: "Telegram",
+ },
},
login: {
"multi-user": {
@@ -181,18 +184,12 @@ const TRANSLATIONS = {
title: "Chế độ trò chuyện",
chat: {
title: "Trò chuyện",
- description:
- "sẽ cung cấp câu trả lời dựa trên kiến thức chung của LLM và ngữ cảnh tài liệu được cung cấp.
Bạn cần sử dụng lệnh @agent để sử dụng các công cụ.",
},
query: {
title: "Truy vấn",
- description:
- "sẽ cung cấp câu trả lời chỉ khi ngữ cảnh của tài liệu được tìm thấy.
Bạn cần sử dụng lệnh @agent để sử dụng các công cụ.",
},
automatic: {
title: "Tự động",
- description:
- "sẽ tự động sử dụng các công cụ nếu mô hình và nhà cung cấp hỗ trợ gọi công cụ gốc.
Nếu không hỗ trợ gọi công cụ gốc, bạn sẽ cần sử dụng lệnh `@agent` để sử dụng các công cụ.",
},
},
history: {
@@ -739,7 +736,6 @@ const TRANSLATIONS = {
see_less: "Xem ít hơn",
see_more: "Xem thêm",
tools: "Dụng cụ",
- browse: "Duyệt",
text_size_label: "Kích thước văn bản",
select_model: "Chọn mẫu",
sources: "Nguồn",
@@ -752,7 +748,6 @@ const TRANSLATIONS = {
edit: "Chỉnh sửa",
publish: "Đăng tải",
stop_generating: "Dừng tạo ra phản hồi",
- pause_tts_speech_message: "Tạm dừng phát giọng đọc của tin nhắn",
slash_commands: "Lệnh tắt/bật",
agent_skills: "Kỹ năng của đại lý",
manage_agent_skills: "Quản lý kỹ năng của đại lý",
@@ -1012,6 +1007,85 @@ const TRANSLATIONS = {
"Bạn hiện không được giao việc nào.\nLiên hệ với quản trị viên của bạn để yêu cầu truy cập vào khu vực làm việc.",
goToWorkspace: 'Chuyển đến khu vực làm việc "{{workspace}}"',
},
+ telegram: {
+ title: "Bot Telegram",
+ description:
+ "Kết nối phiên bản AnythingLLM của bạn với Telegram để bạn có thể trò chuyện với các không gian làm việc của mình từ bất kỳ thiết bị nào.",
+ setup: {
+ step1: {
+ title: "Bước 1: Tạo bot Telegram của bạn",
+ description:
+ "Mở ứng dụng @BotFather trên Telegram, gửi lệnh /newbot đến tài khoản @BotFather, làm theo hướng dẫn và sao chép mã API.",
+ "open-botfather": "Mở BotFather",
+ "instruction-1": "1. Mở liên kết hoặc quét mã QR",
+ "instruction-2":
+ "2. Gửi /newbot đến @BotFather",
+ "instruction-3": "3. Chọn tên và tên người dùng cho bot của bạn",
+ "instruction-4": "4. Sao chép mã API mà bạn nhận được",
+ },
+ step2: {
+ title: "Bước 2: Kết nối bot của bạn",
+ description:
+ "Dán mã API mà bạn nhận được từ @BotFather và chọn không gian làm việc mặc định để bot của bạn có thể trò chuyện.",
+ "bot-token": "Token Bot",
+ "default-workspace": "Không gian làm việc mặc định",
+ "no-workspace":
+ "Không có không gian làm việc nào khả dụng. Một không gian mới sẽ được tạo ra.",
+ connecting: "Kết nối...",
+ "connect-bot": "Bot kết nối",
+ },
+ security: {
+ title: "Các cài đặt bảo mật được khuyến nghị",
+ description:
+ "Để tăng cường bảo mật, hãy cấu hình các cài đặt này trên tài khoản @BotFather.",
+ "disable-groups": "— Ngăn chặn việc thêm bot vào các nhóm",
+ "disable-inline":
+ "— Ngăn chặn việc sử dụng bot trong tìm kiếm trực tiếp.",
+ "obscure-username":
+ "Sử dụng tên người dùng bot không phổ biến để giảm khả năng được tìm thấy.",
+ },
+ "toast-enter-token": "Vui lòng nhập mã token cho bot.",
+ "toast-connect-failed": "Không thể kết nối với trợ lý.",
+ },
+ connected: {
+ status: "Kết nối",
+ "status-disconnected":
+ "Không kết nối — mã token có thể đã hết hạn hoặc không hợp lệ",
+ "placeholder-token": "Dán mã token mới cho bot...",
+ reconnect: "Khôi phục kết nối",
+ workspace: "Không gian làm việc",
+ "bot-link": "Liên kết Bot",
+ "voice-response": "Phản hồi bằng giọng nói",
+ disconnecting: "Ngắt kết nối...",
+ disconnect: "Ngắt kết nối",
+ "voice-text-only": "Chỉ nội dung",
+ "voice-mirror": "Trả lời bằng giọng nói (khi người dùng gửi giọng nói)",
+ "voice-always":
+ "Luôn luôn có thể gửi phản hồi bằng giọng nói (gửi kèm âm thanh trong mỗi phản hồi).",
+ "toast-disconnect-failed": "Không thể ngắt kết nối bot.",
+ "toast-reconnect-failed": "Không thể kết nối lại với trình bot.",
+ "toast-voice-failed": "Không thể cập nhật chế độ giọng nói.",
+ "toast-approve-failed": "Không thể xác nhận tài khoản người dùng.",
+ "toast-deny-failed": "Không thể từ chối yêu cầu của người dùng.",
+ "toast-revoke-failed": "Không thể thu hồi quyền truy cập cho người dùng.",
+ },
+ users: {
+ "pending-title": "Chờ phê duyệt",
+ "pending-description":
+ "Người dùng đang chờ xác nhận. So sánh mã ghép đôi được hiển thị ở đây với mã hiển thị trong cuộc trò chuyện Telegram của họ.",
+ "approved-title": "Người dùng đã được phê duyệt",
+ "approved-description":
+ "Người dùng đã được chấp thuận để trò chuyện với bot của bạn.",
+ user: "Người dùng",
+ "pairing-code": "Mã ghép",
+ "no-pending": "Không có yêu cầu nào đang chờ xử lý.",
+ "no-approved": "Không có người dùng được xác nhận",
+ unknown: "Không xác định",
+ approve: "Chấp thuận",
+ deny: "Từ chối",
+ revoke: "Thu hồi",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js
index e2e9e059..dfaaaa70 100644
--- a/frontend/src/locales/zh/common.js
+++ b/frontend/src/locales/zh/common.js
@@ -48,7 +48,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "工作区名称",
- user: "用户",
selection: "模型选择",
save: "保存更改",
saving: "保存中...",
@@ -102,6 +101,10 @@ const TRANSLATIONS = {
"your-account": "您的账户",
"import-item": "进口商品",
},
+ channels: "频道",
+ "available-channels": {
+ telegram: "电报",
+ },
},
login: {
"multi-user": {
@@ -182,18 +185,12 @@ const TRANSLATIONS = {
title: "聊天模式",
chat: {
title: "聊天",
- description:
- "将提供答案,利用LLM的通用知识和相关文档的上下文信息。您需要使用 `@agent` 命令来使用工具。",
},
query: {
title: "查询",
- description:
- "将在找到文档上下文时提供答案 仅限。您需要使用 @agent 命令来使用工具。",
},
automatic: {
title: "自动",
- description:
- "如果模型和提供商支持原生工具调用,则会自动使用这些工具。
如果不支持原生工具调用,则需要使用 `@agent` 命令来使用工具。",
},
},
history: {
@@ -782,7 +779,6 @@ const TRANSLATIONS = {
see_less: "查看更多",
see_more: "查看更多",
tools: "工具",
- browse: "浏览",
text_size_label: "字体大小",
select_model: "选择型号",
sources: "来源",
@@ -795,7 +791,6 @@ const TRANSLATIONS = {
edit: "编辑",
publish: "出版",
stop_generating: "停止生成回复",
- pause_tts_speech_message: "暂停消息的语音合成(TTS)功能",
slash_commands: "快捷命令",
agent_skills: "代理人技能",
manage_agent_skills: "管理代理人技能",
@@ -951,6 +946,79 @@ const TRANSLATIONS = {
"你目前还没有分配到任何工作区。\n请联系你的管理员请求访问一个工作区。",
goToWorkspace: '前往 "{{workspace}}"',
},
+ telegram: {
+ title: "Telegram 机器人",
+ description:
+ "将您的 AnythingLLM 实例与 Telegram 连接起来,这样您就可以从任何设备与您的工作空间进行聊天。",
+ setup: {
+ step1: {
+ title: "第一步:创建您的 Telegram 机器人",
+ description:
+ "打开 Telegram 上的 @BotFather,发送 `/newbot` 到 @BotFather,按照提示操作,并复制 API 令牌。",
+ "open-botfather": "启动 BotFather",
+ "instruction-1": "1. 打开链接或扫描二维码",
+ "instruction-2":
+ "2. 将 /newbot 发送给 @BotFather",
+ "instruction-3": "3. 为您的机器人选择一个名称和用户名",
+ "instruction-4": "4. 复制您收到的 API 令牌",
+ },
+ step2: {
+ title: "步骤 2:连接您的机器人",
+ description:
+ "将您从 @BotFather 获得的 API 令牌粘贴到指定位置,并选择一个默认的工作区,以便您的机器人可以进行对话。",
+ "bot-token": "机器人代币",
+ "default-workspace": "默认工作区",
+ "no-workspace": "目前没有可用的工作空间。将会创建一个新的工作空间。",
+ connecting: "正在连接...",
+ "connect-bot": "连接机器人",
+ },
+ security: {
+ title: "推荐的安全设置",
+ description: "为了进一步增强安全性,请在 @BotFather 中配置这些设置。",
+ "disable-groups": "— 阻止机器人加入群组",
+ "disable-inline": "— 阻止机器人被用于内联搜索",
+ "obscure-username":
+ "使用一个不显眼的机器人用户名,以降低其被发现的可能性。",
+ },
+ "toast-enter-token": "请您输入一个机器人令牌。",
+ "toast-connect-failed": "未能连接机器人。",
+ },
+ connected: {
+ status: "连接",
+ "status-disconnected": "未连接—— 令牌可能已过期或无效",
+ "placeholder-token": "粘贴新的机器人令牌...",
+ reconnect: "重新连接",
+ workspace: "工作空间",
+ "bot-link": "机器人链接",
+ "voice-response": "语音响应",
+ disconnecting: "断开连接...",
+ disconnect: "断开",
+ "voice-text-only": "仅提供文字",
+ "voice-mirror": "回声(当用户发送语音时,会以语音形式回复)",
+ "voice-always": "请务必在回复中添加语音(发送音频)。",
+ "toast-disconnect-failed": "未能成功断开机器人。",
+ "toast-reconnect-failed": "机器人连接失败。",
+ "toast-voice-failed": "无法更新语音模式。",
+ "toast-approve-failed": "未能批准用户。",
+ "toast-deny-failed": "未能拒绝用户请求。",
+ "toast-revoke-failed": "未能撤销用户权限。",
+ },
+ users: {
+ "pending-title": "待审批",
+ "pending-description":
+ "等待验证的用户。请将此处显示的配对代码与他们在 Telegram 聊天中显示的配对代码进行匹配。",
+ "approved-title": "已批准的用户",
+ "approved-description": "已获得批准,可以与您的机器人进行对话的用户。",
+ user: "用户",
+ "pairing-code": "配对代码",
+ "no-pending": "目前没有待处理的请求",
+ "no-approved": "未批准的用户",
+ unknown: "未知",
+ approve: "批准",
+ deny: "否认",
+ revoke: "撤销",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/locales/zh_TW/common.js b/frontend/src/locales/zh_TW/common.js
index ab2bc448..5297fb88 100644
--- a/frontend/src/locales/zh_TW/common.js
+++ b/frontend/src/locales/zh_TW/common.js
@@ -48,7 +48,6 @@ const TRANSLATIONS = {
},
common: {
"workspaces-name": "工作區名稱",
- user: "使用者",
selection: "模型選擇",
saving: "儲存中...",
save: "儲存變更",
@@ -102,6 +101,10 @@ const TRANSLATIONS = {
"your-account": "您的帳戶",
"import-item": "匯入項目",
},
+ channels: "頻道",
+ "available-channels": {
+ telegram: "電訊",
+ },
},
login: {
"multi-user": {
@@ -174,18 +177,12 @@ const TRANSLATIONS = {
title: "對話模式",
chat: {
title: "對話",
- description:
- "將提供答案,利用 LLM 的一般知識和相關文件內容。您需要使用 `@agent` 命令來使用工具。",
},
query: {
title: "查詢",
- description:
- "將提供答案,僅在找到文件上下文時 。您需要使用 @agent 指令來使用工具。",
},
automatic: {
title: "自動",
- description:
- "如果模型和供應商支援原生工具調用,則系統會自動使用這些工具。
如果原生工具調用不受支援,您需要使用 `@agent` 命令來使用工具。",
},
},
history: {
@@ -692,7 +689,6 @@ const TRANSLATIONS = {
see_less: "顯示較少",
see_more: "查看更多",
tools: "工具",
- browse: "瀏覽",
text_size_label: "文字大小",
select_model: "選擇模型",
sources: "來源",
@@ -705,7 +701,6 @@ const TRANSLATIONS = {
edit: "編輯",
publish: "發佈",
stop_generating: "停止產生回應",
- pause_tts_speech_message: "暫停語音合成的訊息",
slash_commands: "斜線指令",
agent_skills: "智慧代理人技能",
manage_agent_skills: "管理智慧代理人技能",
@@ -944,6 +939,79 @@ const TRANSLATIONS = {
"您目前尚未被分配到任何工作區。\n請聯絡您的管理員以申請工作區的存取權限。",
goToWorkspace: '前往 "{{workspace}}"',
},
+ telegram: {
+ title: "Telegram 機器人",
+ description:
+ "將您的 AnythingLLM 實例連接到 Telegram,以便您可以在任何裝置上與您的工作空間進行對話。",
+ setup: {
+ step1: {
+ title: "第一步:建立您的 Telegram 機器人",
+ description:
+ '在 Telegram 中開啟 @BotFather,將 "/newbot" 訊息發送至 @BotFather,按照指示操作,並複製 API 令牌。',
+ "open-botfather": "開啟 BotFather",
+ "instruction-1": "1. 點擊連結或掃描 QR 碼",
+ "instruction-2":
+ "2. 將 /newbot 傳送至 @BotFather",
+ "instruction-3": "3. 為您的機器人選擇一個名稱和使用者名稱。",
+ "instruction-4": "4. 複製您收到的 API 令牌",
+ },
+ step2: {
+ title: "步驟 2:連接您的機器人",
+ description:
+ "請將您從 @BotFather 處獲得的 API 令牌複製並貼上,然後選擇一個預設的工作空間,讓您的機器人與其對話。",
+ "bot-token": "機器人代幣",
+ "default-workspace": "預設工作空間",
+ "no-workspace": "目前沒有可用的工作空間。將會創建一個新的工作空間。",
+ connecting: "正在連接...",
+ "connect-bot": "連線機器人",
+ },
+ security: {
+ title: "建議的安全設定",
+ description: "為了額外保障,請在 @BotFather 中設定這些選項。",
+ "disable-groups": "— 阻止自動程式加入群組",
+ "disable-inline": "— 阻止機器人被用於內嵌式搜尋",
+ "obscure-username":
+ "使用一個不顯眼的機器人帳號名稱,以降低被發現的機會。",
+ },
+ "toast-enter-token": "請輸入機器人憑證。",
+ "toast-connect-failed": "無法連接機器人。",
+ },
+ connected: {
+ status: "連接",
+ "status-disconnected": "無法連接 — 可能是 token 已經過期或無效",
+ "placeholder-token": "黏貼新的機器人代碼...",
+ reconnect: "重新建立聯繫",
+ workspace: "工作空間",
+ "bot-link": "機器人連結",
+ "voice-response": "語音回應",
+ disconnecting: "斷線...",
+ disconnect: "斷開連接",
+ "voice-text-only": "僅提供文字",
+ "voice-mirror": "語音回覆 (使用者發送語音時,系統會回覆語音)",
+ "voice-always": "請務必在回覆中加入語音 (發送音訊)。",
+ "toast-disconnect-failed": "未能成功斷開機器人。",
+ "toast-reconnect-failed": "無法重新連線機器人。",
+ "toast-voice-failed": "無法更新語音模式。",
+ "toast-approve-failed": "無法驗證使用者。",
+ "toast-deny-failed": "未能阻止使用者。",
+ "toast-revoke-failed": "未能取消使用者權限。",
+ },
+ users: {
+ "pending-title": "待審核",
+ "pending-description":
+ "等待驗證的使用者。請將這裡顯示的配對碼與他們在 Telegram 聊天中顯示的配對碼對齊。",
+ "approved-title": "已授權的使用者",
+ "approved-description": "已獲得批准,可以與您的機器人進行對話的使用者。",
+ user: "使用者",
+ "pairing-code": "編碼組合",
+ "no-pending": "目前沒有待處理的請求",
+ "no-approved": "目前沒有已授權的使用者",
+ unknown: "未知的",
+ approve: "批准",
+ deny: "拒絕",
+ revoke: "撤銷",
+ },
+ },
};
export default TRANSLATIONS;
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 2224f2e5..22b375b4 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -372,6 +372,15 @@ const router = createBrowserRouter([
return { element: };
},
},
+ {
+ path: "/settings/external-connections/telegram",
+ lazy: async () => {
+ const { default: TelegramBotSettings } = await import(
+ "@/pages/GeneralSettings/Connections/TelegramBot"
+ );
+ return { element: };
+ },
+ },
// Catch-all route for 404s
{
path: "*",
diff --git a/frontend/src/models/telegram.js b/frontend/src/models/telegram.js
new file mode 100644
index 00000000..d6f45d8e
--- /dev/null
+++ b/frontend/src/models/telegram.js
@@ -0,0 +1,176 @@
+import { API_BASE } from "@/utils/constants";
+import { baseHeaders } from "@/utils/request";
+
+const Telegram = {
+ /**
+ * Get the current Telegram bot configuration.
+ * @returns {Promise<{config: object|null, error: string|null}>}
+ */
+ getConfig: async function () {
+ return await fetch(`${API_BASE}/telegram/config`, {
+ headers: baseHeaders(),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { config: null, error: e.message };
+ });
+ },
+
+ /**
+ * Connect and start the Telegram bot with given token and workspace.
+ * @param {string} botToken - The bot API token from BotFather.
+ * @param {string} workspaceSlug - The default workspace slug.
+ * @returns {Promise<{success: boolean, bot_username: string|null, error: string|null}>}
+ */
+ connect: async function (botToken, workspaceSlug) {
+ return await fetch(`${API_BASE}/telegram/connect`, {
+ method: "POST",
+ headers: baseHeaders(),
+ body: JSON.stringify({
+ bot_token: botToken,
+ default_workspace: workspaceSlug,
+ }),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { success: false, error: e.message };
+ });
+ },
+
+ /**
+ * Disconnect and stop the Telegram bot.
+ * @returns {Promise<{success: boolean, error: string|null}>}
+ */
+ disconnect: async function () {
+ return await fetch(`${API_BASE}/telegram/disconnect`, {
+ method: "POST",
+ headers: baseHeaders(),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { success: false, error: e.message };
+ });
+ },
+
+ /**
+ * Get the current bot connection status.
+ * @returns {Promise<{active: boolean, bot_username: string|null}>}
+ */
+ status: async function () {
+ return await fetch(`${API_BASE}/telegram/status`, {
+ headers: baseHeaders(),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { active: false, bot_username: null };
+ });
+ },
+
+ /**
+ * Get pending pairing requests.
+ * @returns {Promise<{users: Array}>}
+ */
+ getPendingUsers: async function () {
+ return await fetch(`${API_BASE}/telegram/pending-users`, {
+ headers: baseHeaders(),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { users: [] };
+ });
+ },
+
+ /**
+ * Get approved users list.
+ * @returns {Promise<{users: Array}>}
+ */
+ getApprovedUsers: async function () {
+ return await fetch(`${API_BASE}/telegram/approved-users`, {
+ headers: baseHeaders(),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { users: [] };
+ });
+ },
+
+ /**
+ * Approve a pending user.
+ * @param {string} chatId
+ * @returns {Promise<{success: boolean, error: string|null}>}
+ */
+ approveUser: async function (chatId) {
+ return await fetch(`${API_BASE}/telegram/approve-user`, {
+ method: "POST",
+ headers: baseHeaders(),
+ body: JSON.stringify({ chatId }),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { success: false, error: e.message };
+ });
+ },
+
+ /**
+ * Deny a pending user.
+ * @param {string} chatId
+ * @returns {Promise<{success: boolean, error: string|null}>}
+ */
+ denyUser: async function (chatId) {
+ return await fetch(`${API_BASE}/telegram/deny-user`, {
+ method: "POST",
+ headers: baseHeaders(),
+ body: JSON.stringify({ chatId }),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { success: false, error: e.message };
+ });
+ },
+
+ /**
+ * Update the Telegram bot configuration.
+ * @param {object} updates - Config fields to update (e.g. voice_response_mode).
+ * @returns {Promise<{success: boolean, error: string|null}>}
+ */
+ updateConfig: async function (updates) {
+ return await fetch(`${API_BASE}/telegram/update-config`, {
+ method: "POST",
+ headers: baseHeaders(),
+ body: JSON.stringify(updates),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { success: false, error: e.message };
+ });
+ },
+
+ /**
+ * Revoke an approved user.
+ * @param {string} chatId
+ * @returns {Promise<{success: boolean, error: string|null}>}
+ */
+ revokeUser: async function (chatId) {
+ return await fetch(`${API_BASE}/telegram/revoke-user`, {
+ method: "POST",
+ headers: baseHeaders(),
+ body: JSON.stringify({ chatId }),
+ })
+ .then((res) => res.json())
+ .catch((e) => {
+ console.error(e);
+ return { success: false, error: e.message };
+ });
+ },
+};
+
+export default Telegram;
diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersTable/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersTable/index.jsx
new file mode 100644
index 00000000..1dfe6ec7
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/UsersTable/index.jsx
@@ -0,0 +1,153 @@
+import { useTranslation } from "react-i18next";
+import Telegram from "@/models/telegram";
+import showToast from "@/utils/toast";
+
+export default function UsersTable({
+ title,
+ description,
+ users = [],
+ isPending = false,
+ fetchUsers = () => {},
+}) {
+ const { t } = useTranslation();
+ if (users.length === 0) return null;
+ const colCount = isPending ? 4 : 3;
+
+ async function handleApprove(chatId) {
+ const res = await Telegram.approveUser(chatId);
+ if (!res.success) {
+ showToast(
+ res.error || t("telegram.connected.toast-approve-failed"),
+ "error"
+ );
+ return;
+ }
+ fetchUsers();
+ }
+
+ async function handleDeny(chatId) {
+ const res = await Telegram.denyUser(chatId);
+ if (!res.success) {
+ showToast(
+ res.error || t("telegram.connected.toast-deny-failed"),
+ "error"
+ );
+ return;
+ }
+ fetchUsers();
+ }
+
+ async function handleRevoke(chatId) {
+ const res = await Telegram.revokeUser(chatId);
+ if (!res.success) {
+ showToast(
+ res.error || t("telegram.connected.toast-revoke-failed"),
+ "error"
+ );
+ return;
+ }
+ fetchUsers();
+ }
+
+ return (
+
+ {title}
+ {description}
+
+
+
+
+
+ {t("telegram.users.user")}
+
+ {isPending && (
+
+ {t("telegram.users.pairing-code")}
+
+ )}
+
+ {" "}
+
+
+
+
+ {users.length === 0 ? (
+
+
+ {isPending
+ ? t("telegram.users.no-pending")
+ : t("telegram.users.no-approved")}
+
+
+ ) : (
+ users.map((user) => {
+ const chatId = typeof user === "string" ? user : user.chatId;
+ const username = user.telegramUsername || user.username || null;
+ const firstName = user.firstName || null;
+ const displayName = username
+ ? `@${username}`
+ : firstName || t("telegram.users.unknown");
+ const code = user.code;
+ return (
+
+
+
+ {displayName}
+
+
+ {isPending && (
+
+
+ {code}
+
+
+ )}
+
+ {isPending ? (
+ <>
+ handleApprove(chatId)}
+ className="hover:light:bg-green-50 hover:light:text-green-500 hover:text-green-300"
+ >
+ {t("telegram.users.approve")}
+
+ handleDeny(chatId)}
+ className="hover:light:bg-red-50 hover:light:text-red-500 hover:text-red-300"
+ >
+ {t("telegram.users.deny")}
+
+ >
+ ) : (
+ handleRevoke(chatId)}
+ className="hover:light:bg-red-50"
+ >
+ {t("telegram.users.revoke")}
+
+ )}
+
+
+ );
+ })
+ )}
+
+
+
+
+ );
+}
+
+function ActionButton({ onClick, className = "", children }) {
+ return (
+
+ );
+}
diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx
new file mode 100644
index 00000000..7bc0fc20
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/ConnectedView/index.jsx
@@ -0,0 +1,313 @@
+import { useEffect, useState, useCallback } from "react";
+import {
+ ArrowSquareOut,
+ CircleNotch,
+ Eye,
+ EyeSlash,
+ TelegramLogo,
+} from "@phosphor-icons/react";
+import Telegram from "@/models/telegram";
+import showToast from "@/utils/toast";
+import UsersTable from "./UsersTable";
+import { useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
+
+export default function ConnectedView({
+ config,
+ workspaces,
+ onDisconnected,
+ onReconnected,
+}) {
+ const { t } = useTranslation();
+ const connected = config.connected;
+ const [newToken, setNewToken] = useState("");
+ const [pendingUsers, setPendingUsers] = useState([]);
+ const [approvedUsers, setApprovedUsers] = useState([]);
+ const workspaceName =
+ workspaces.find((ws) => ws.slug === config.default_workspace)?.name ||
+ config.default_workspace;
+
+ const fetchUsers = useCallback(async () => {
+ const [pending, approved] = await Promise.all([
+ Telegram.getPendingUsers(),
+ Telegram.getApprovedUsers(),
+ ]);
+ setPendingUsers(pending?.users || []);
+ setApprovedUsers(approved?.users || []);
+ }, []);
+
+ useEffect(() => {
+ fetchUsers();
+ const interval = setInterval(fetchUsers, 5_000);
+ return () => clearInterval(interval);
+ }, [fetchUsers]);
+
+ if (!connected) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ @{config.bot_username}
+
+
+ {t("telegram.connected.status")}
+
+
+
+
+
+
+ {/*
+ Disabled for now - works fine, but I am not sure we want to enabled this feature.
+ How many people really need a REPLY with voice mode? Even then, we should support on device TTS
+ and more it out the frontend so people can do voice gen without having to pay for it.
+ */}
+ {/* */}
+
+
+
+
+
+
+
+ );
+}
+
+function BotLink({ username }) {
+ const { t } = useTranslation();
+ return (
+
+
+ {t("telegram.connected.bot-link")}
+
+
+ t.me/{username}
+
+
+
+ );
+}
+
+function DisconnectButton({ onDisconnected }) {
+ const { t } = useTranslation();
+ const [disconnecting, setDisconnecting] = useState(false);
+
+ async function handleDisconnect() {
+ setDisconnecting(true);
+ const res = await Telegram.disconnect();
+ setDisconnecting(false);
+
+ if (!res.success) {
+ showToast(
+ res.error || t("telegram.connected.toast-disconnect-failed"),
+ "error"
+ );
+ return;
+ }
+ onDisconnected();
+ }
+
+ return (
+
+ );
+}
+
+function WorkspaceName({ name }) {
+ const { t } = useTranslation();
+ return (
+
+
+ {t("telegram.connected.workspace")}
+
+ {name}
+
+ );
+}
+
+function DisconnectedView({ config, onReconnected, newToken, setNewToken }) {
+ const { t } = useTranslation();
+ const [reconnecting, setReconnecting] = useState(false);
+ const [showToken, setShowToken] = useState(false);
+ const Icon = showToken ? Eye : EyeSlash;
+
+ async function handleReconnect(e) {
+ e.preventDefault();
+ if (!newToken.trim()) return;
+ setReconnecting(true);
+ const res = await Telegram.connect(
+ newToken.trim(),
+ config.default_workspace
+ );
+ setReconnecting(false);
+ if (!res.success)
+ return showToast(
+ res.error || t("telegram.connected.toast-reconnect-failed"),
+ "error"
+ );
+
+ setNewToken("");
+ onReconnected({
+ active: true,
+ connected: true,
+ bot_username: res.bot_username,
+ default_workspace: config.default_workspace,
+ });
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ @{config.bot_username}
+
+
+ {t("telegram.connected.status-disconnected")}
+
+
+
+
+
+
+
+ );
+}
+
+/**
+This code is disabled for now - works fine, but I am not sure we want to enabled this feature.
+How many people really need a REPLY with voice mode? Even then, we should support on device TTS
+and more it out the frontend so people can do voice gen without having to pay for it.
+
+When we do enabled this, we should uncomment this code and remove the disabled comment.
+
+const getVoiceModeOptions = (t) => {
+ return [
+ { value: "text_only", label: t("telegram.connected.voice-text-only") },
+ { value: "mirror", label: t("telegram.connected.voice-mirror") },
+ { value: "always_voice", label: t("telegram.connected.voice-always") },
+ ];
+};
+
+function VoiceModeSelector({ config }) {
+ const { t } = useTranslation();
+ const [voiceMode, setVoiceMode] = useState(
+ config.voice_response_mode || "text_only"
+ );
+
+ async function handleVoiceModeChange(e) {
+ const mode = e.target.value;
+ setVoiceMode(mode);
+ const res = await Telegram.updateConfig({ voice_response_mode: mode });
+ if (!res.success) {
+ showToast(
+ res.error || t("telegram.connected.toast-voice-failed"),
+ "error"
+ );
+ setVoiceMode(config.voice_response_mode || "text_only");
+ }
+ }
+
+ return (
+
+
+ {t("telegram.connected.voice-response")}
+
+
+
+ );
+}
+
+*/
diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx
new file mode 100644
index 00000000..37bbbf98
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/CreateBotSection/index.jsx
@@ -0,0 +1,118 @@
+import { QRCodeSVG } from "qrcode.react";
+import { ShieldCheck, TelegramLogo } from "@phosphor-icons/react";
+import Logo from "@/media/logo/anything-llm-infinity.png";
+import { Trans, useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
+
+const BOTFATHER_URL = "https://t.me/BotFather";
+
+export default function CreateBotSection() {
+ const { t } = useTranslation();
+ const qrSize = 180;
+ const logoSize = { width: 35 * 1.2, height: 22 * 1.2 };
+
+ return (
+
+
+
+ {t("telegram.setup.step1.title")}
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ {t("telegram.setup.step1.open-botfather")}
+
+
+ {t("telegram.setup.step1.instruction-1")}
+
+
+ ),
+ }}
+ />
+
+ {t("telegram.setup.step1.instruction-3")}
+ {t("telegram.setup.step1.instruction-4")}
+
+
+
+
+
+
+ );
+}
+
+function SecurityTips() {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+ {t("telegram.setup.security.title")}
+
+
+
+ {t("telegram.setup.security.description")}
+
+
+ -
+
+ Disable Groups
+ {" "}
+ {t("telegram.setup.security.disable-groups")}
+
+ -
+
+ Disable Inline
+ {" "}
+ {t("telegram.setup.security.disable-inline")}
+
+ - {t("telegram.setup.security.obscure-username")}
+
+
+ );
+}
diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx
new file mode 100644
index 00000000..62385f9a
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/SetupView/index.jsx
@@ -0,0 +1,159 @@
+import { useState } from "react";
+import {
+ CircleNotch,
+ Eye,
+ EyeSlash,
+ TelegramLogo,
+} from "@phosphor-icons/react";
+import Telegram from "@/models/telegram";
+import showToast from "@/utils/toast";
+import CreateBotSection from "./CreateBotSection";
+import { useTranslation } from "react-i18next";
+
+export default function SetupView({ workspaces, onConnected }) {
+ const { t } = useTranslation();
+ const [botToken, setBotToken] = useState("");
+ const [selectedWorkspace, setSelectedWorkspace] = useState(
+ workspaces[0]?.slug || ""
+ );
+ const [connecting, setConnecting] = useState(false);
+
+ async function handleConnect(e) {
+ e.preventDefault();
+ if (!botToken.trim())
+ return showToast(t("telegram.setup.toast-enter-token"), "error");
+
+ setConnecting(true);
+ const res = await Telegram.connect(botToken.trim(), selectedWorkspace);
+ setConnecting(false);
+
+ if (!res.success) {
+ showToast(res.error || t("telegram.setup.toast-connect-failed"), "error");
+ return;
+ }
+ onConnected({
+ active: true,
+ connected: true,
+ bot_username: res.bot_username,
+ default_workspace: selectedWorkspace || null,
+ });
+ }
+
+ return (
+
+
+
+
+ );
+}
+
+function BotTokenInput({ botToken, setBotToken }) {
+ const { t } = useTranslation();
+ const [showToken, setShowToken] = useState(false);
+ const Icon = showToken ? Eye : EyeSlash;
+
+ return (
+
+
+
+ setBotToken(e.target.value)}
+ placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v..."
+ className="bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5 pr-10"
+ autoComplete="off"
+ />
+ {botToken.length > 0 && (
+
+ )}
+
+
+ );
+}
+
+function WorkspaceSelect({
+ workspaces,
+ selectedWorkspace,
+ setSelectedWorkspace,
+}) {
+ const { t } = useTranslation();
+
+ if (!workspaces.length) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/GeneralSettings/Connections/TelegramBot/index.jsx b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/index.jsx
new file mode 100644
index 00000000..0284dc5d
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/Connections/TelegramBot/index.jsx
@@ -0,0 +1,97 @@
+import { useEffect, useState } from "react";
+import Sidebar from "@/components/SettingsSidebar";
+import { isMobile } from "react-device-detect";
+import { CircleNotch } from "@phosphor-icons/react";
+import Workspace from "@/models/workspace";
+import Telegram from "@/models/telegram";
+import ConnectedView from "./ConnectedView";
+import SetupView from "./SetupView";
+import { useTranslation } from "react-i18next";
+import System from "@/models/system";
+import paths from "@/utils/paths";
+
+export default function TelegramBotSettings() {
+ const [loading, setLoading] = useState(true);
+ const [config, setConfig] = useState(null);
+ const [workspaces, setWorkspaces] = useState([]);
+
+ useEffect(() => {
+ async function fetchData() {
+ const [isMultiUserMode, configRes, allWorkspaces] = await Promise.all([
+ System.isMultiUserMode(),
+ Telegram.getConfig(),
+ Workspace.all(),
+ ]);
+
+ if (isMultiUserMode) window.location = paths.home();
+ setConfig(configRes?.config || null);
+ setWorkspaces(allWorkspaces || []);
+ setLoading(false);
+ }
+ fetchData();
+ }, []);
+
+ const handleConnected = (newConfig) => setConfig(newConfig);
+ const handleDisconnected = () => setConfig(null);
+
+ if (loading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ const hasConfig = config?.active && config?.bot_username;
+ if (!hasConfig) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
+
+function ConnectionsLayout({ children, fullPage = false }) {
+ const { t } = useTranslation();
+ return (
+
+
+
+ {fullPage ? (
+
+
+
+
+ {t("telegram.title")}
+
+
+
+ {t("telegram.description")}
+
+
+ {children}
+
+ ) : (
+ children
+ )}
+
+
+ );
+}
diff --git a/frontend/src/utils/paths.js b/frontend/src/utils/paths.js
index 814e3a84..78124e37 100644
--- a/frontend/src/utils/paths.js
+++ b/frontend/src/utils/paths.js
@@ -170,6 +170,9 @@ export default {
mobileConnections: () => {
return `/settings/mobile-connections`;
},
+ telegram: () => {
+ return `/settings/external-connections/telegram`;
+ },
},
agents: {
builder: () => {
diff --git a/server/endpoints/telegram.js b/server/endpoints/telegram.js
new file mode 100644
index 00000000..b04bf881
--- /dev/null
+++ b/server/endpoints/telegram.js
@@ -0,0 +1,307 @@
+const {
+ ExternalCommunicationConnector,
+} = require("../models/externalCommunicationConnector");
+const { Telemetry } = require("../models/telemetry");
+const { TelegramBotService } = require("../utils/telegramBot");
+const { validatedRequest } = require("../utils/middleware/validatedRequest");
+const { isSingleUserMode } = require("../utils/middleware/multiUserProtected");
+const { reqBody } = require("../utils/http");
+const { EventLogs } = require("../models/eventLogs");
+const { Workspace } = require("../models/workspace");
+const { encryptToken } = require("../utils/telegramBot/utils");
+
+function telegramEndpoints(app) {
+ if (!app) return;
+
+ app.get(
+ "/telegram/config",
+ [validatedRequest, isSingleUserMode],
+ async (_request, response) => {
+ try {
+ const connector = await ExternalCommunicationConnector.get("telegram");
+ if (!connector) {
+ return response.status(200).json({ config: null });
+ }
+
+ const service = new TelegramBotService();
+ return response.status(200).json({
+ config: {
+ active: connector.active,
+ connected: service.isRunning,
+ bot_username: connector.config.bot_username || null,
+ default_workspace: connector.config.default_workspace || null,
+ voice_response_mode:
+ connector.config.voice_response_mode || "text_only",
+ },
+ });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ /**
+ * Verify token, save config, and start the Telegram bot.
+ */
+ app.post(
+ "/telegram/connect",
+ [validatedRequest, isSingleUserMode],
+ async (request, response) => {
+ try {
+ const { bot_token, default_workspace = null } = reqBody(request);
+ if (!bot_token) {
+ return response.status(400).json({
+ success: false,
+ error: "Bot token is required.",
+ });
+ }
+
+ // Verify the token with Telegram API
+ const verification = await TelegramBotService.verifyToken(
+ String(bot_token)
+ );
+ if (!verification.valid) {
+ return response.status(400).json({
+ success: false,
+ error: `Invalid bot token: ${verification.error}`,
+ });
+ }
+
+ let workspaceSlug = null;
+ if (default_workspace) workspaceSlug = String(default_workspace);
+ else {
+ const workspaces = await Workspace.where({}, 1);
+ if (workspaces.length) workspaceSlug = workspaces[0].slug;
+ else {
+ const { workspace } = await Workspace.new(
+ `${verification.username} Workspace`,
+ null,
+ { chatMode: "automatic" }
+ );
+ if (workspace) workspaceSlug = workspace.slug;
+ }
+ }
+
+ if (!workspaceSlug) {
+ return response.status(400).json({
+ success: false,
+ error: "No workspace found or could be created.",
+ });
+ }
+
+ // Preserve approved users when reconnecting with a new token
+ const existing = await ExternalCommunicationConnector.get("telegram");
+ const storedConfig = {
+ bot_username: verification.username,
+ default_workspace: workspaceSlug,
+ approved_users: existing?.config?.approved_users || [],
+ voice_response_mode:
+ existing?.config?.voice_response_mode || "text_only",
+ };
+
+ // Save config with encrypted token
+ const { error } = await ExternalCommunicationConnector.upsert(
+ "telegram",
+ {
+ ...storedConfig,
+ bot_token: encryptToken(String(bot_token)),
+ active: true,
+ }
+ );
+ if (error) return response.status(500).json({ success: false, error });
+
+ // Start the bot with the plaintext token
+ const service = new TelegramBotService();
+ await service.start({ ...storedConfig, bot_token: String(bot_token) });
+
+ await EventLogs.logEvent("telegram_bot_connected", {
+ bot_username: verification.username,
+ });
+ await Telemetry.sendTelemetry("telegram_bot_connected");
+ return response.status(200).json({
+ success: true,
+ bot_username: verification.username,
+ });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.post(
+ "/telegram/disconnect",
+ [validatedRequest, isSingleUserMode],
+ async (_request, response) => {
+ try {
+ const service = new TelegramBotService();
+ service.stop();
+ await ExternalCommunicationConnector.delete("telegram");
+ await EventLogs.logEvent("telegram_bot_disconnected");
+ return response.status(200).json({ success: true });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.get(
+ "/telegram/status",
+ [validatedRequest, isSingleUserMode],
+ async (_request, response) => {
+ try {
+ const connector = await ExternalCommunicationConnector.get("telegram");
+ const service = new TelegramBotService();
+ return response.status(200).json({
+ active: connector?.active && service.isRunning,
+ bot_username: connector?.config?.bot_username || null,
+ });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.get(
+ "/telegram/pending-users",
+ [validatedRequest, isSingleUserMode],
+ async (_request, response) => {
+ try {
+ const service = new TelegramBotService();
+ return response
+ .status(200)
+ .json({ users: service.pendingPairings || [] });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.get(
+ "/telegram/approved-users",
+ [validatedRequest, isSingleUserMode],
+ async (_request, response) => {
+ try {
+ const connector = await ExternalCommunicationConnector.get("telegram");
+ const approved = connector?.config?.approved_users || [];
+ return response.status(200).json({ users: approved });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.post(
+ "/telegram/approve-user",
+ [validatedRequest, isSingleUserMode],
+ async (request, response) => {
+ try {
+ const { chatId } = reqBody(request);
+ if (!chatId)
+ return response
+ .status(400)
+ .json({ success: false, error: "chatId is required." });
+
+ const service = new TelegramBotService();
+ await service.approvePendingUser(chatId);
+ await EventLogs.logEvent("telegram_user_approved", { chatId });
+ return response.status(200).json({ success: true });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.post(
+ "/telegram/deny-user",
+ [validatedRequest, isSingleUserMode],
+ async (request, response) => {
+ try {
+ const { chatId } = reqBody(request);
+ if (!chatId)
+ return response
+ .status(400)
+ .json({ success: false, error: "chatId is required." });
+
+ const service = new TelegramBotService();
+ await service.denyPendingUser(chatId);
+ await EventLogs.logEvent("telegram_user_denied", { chatId });
+ return response.status(200).json({ success: true });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.post(
+ "/telegram/revoke-user",
+ [validatedRequest, isSingleUserMode],
+ async (request, response) => {
+ try {
+ const { chatId } = reqBody(request);
+ if (!chatId)
+ return response
+ .status(400)
+ .json({ success: false, error: "chatId is required." });
+
+ const service = new TelegramBotService();
+ await service.revokeExistingUser(chatId);
+ await EventLogs.logEvent("telegram_user_revoked", { chatId });
+ return response.status(200).json({ success: true });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+
+ app.post(
+ "/telegram/update-config",
+ [validatedRequest, isSingleUserMode],
+ async (request, response) => {
+ try {
+ const { voice_response_mode } = reqBody(request);
+ const updates = {};
+
+ if (
+ voice_response_mode &&
+ ["text_only", "mirror", "always_voice"].includes(voice_response_mode)
+ ) {
+ updates.voice_response_mode = voice_response_mode;
+ }
+
+ if (Object.keys(updates).length === 0) {
+ return response
+ .status(400)
+ .json({ success: false, error: "No valid updates provided." });
+ }
+
+ const { error } = await ExternalCommunicationConnector.updateConfig(
+ "telegram",
+ updates
+ );
+ if (error) {
+ return response.status(500).json({ success: false, error });
+ }
+
+ // Update the running bot's config so changes take effect immediately
+ const service = new TelegramBotService();
+ if (service.isRunning) service.updateConfig(updates);
+
+ return response.status(200).json({ success: true });
+ } catch (e) {
+ console.error(e.message, e);
+ response.sendStatus(500);
+ }
+ }
+ );
+}
+
+module.exports = { telegramEndpoints };
diff --git a/server/index.js b/server/index.js
index 761fd4e1..422b0193 100644
--- a/server/index.js
+++ b/server/index.js
@@ -30,6 +30,7 @@ const { agentFlowEndpoints } = require("./endpoints/agentFlows");
const { mcpServersEndpoints } = require("./endpoints/mcpServers");
const { mobileEndpoints } = require("./endpoints/mobile");
const { webPushEndpoints } = require("./endpoints/webPush");
+const { telegramEndpoints } = require("./endpoints/telegram");
const { httpLogger } = require("./middleware/httpLogger");
const app = express();
const apiRouter = express.Router();
@@ -81,6 +82,7 @@ agentFlowEndpoints(apiRouter);
mcpServersEndpoints(apiRouter);
mobileEndpoints(apiRouter);
webPushEndpoints(apiRouter);
+telegramEndpoints(apiRouter);
// Externally facing embedder endpoints
embeddedEndpoints(apiRouter);
diff --git a/server/jobs/handle-telegram-chat.js b/server/jobs/handle-telegram-chat.js
new file mode 100644
index 00000000..b7f60ea8
--- /dev/null
+++ b/server/jobs/handle-telegram-chat.js
@@ -0,0 +1,64 @@
+// Suppress deprecated content-type warning when sending files via the Telegram bot API.
+// https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#file-options-metadata
+process.env.NTBA_FIX_350 = 1;
+const TelegramBot = require("node-telegram-bot-api");
+const { log, conclude } = require("./helpers/index.js");
+const { Workspace } = require("../models/workspace");
+const { WorkspaceThread } = require("../models/workspaceThread");
+const { streamResponse } = require("../utils/telegramBot/chat/stream");
+
+process.on("message", async (payload) => {
+ const {
+ botToken,
+ chatId,
+ workspaceSlug,
+ threadSlug,
+ message,
+ attachments = [],
+ voiceResponse = false,
+ } = payload;
+
+ try {
+ const bot = new TelegramBot(botToken, { polling: false });
+ const ctx = {
+ bot,
+ log: (text, ...args) =>
+ log(args.length ? `${text} ${args.join(" ")}` : text),
+ };
+
+ const workspace = await Workspace.get({ slug: workspaceSlug });
+ if (!workspace) {
+ await bot.sendMessage(
+ chatId,
+ "No workspace configured. Use /switch to select one."
+ );
+ conclude();
+ return;
+ }
+
+ const thread = threadSlug
+ ? await WorkspaceThread.get({ slug: threadSlug })
+ : null;
+
+ await streamResponse({
+ ctx,
+ chatId,
+ workspace,
+ thread,
+ message,
+ attachments,
+ voiceResponse,
+ });
+ } catch (error) {
+ log(`Telegram chat error: ${error.message}`);
+ try {
+ const bot = new TelegramBot(botToken, { polling: false });
+ await bot.sendMessage(
+ chatId,
+ "Sorry, something went wrong. Please try again."
+ );
+ } catch {}
+ } finally {
+ conclude();
+ }
+});
diff --git a/server/models/externalCommunicationConnector.js b/server/models/externalCommunicationConnector.js
new file mode 100644
index 00000000..21c025f4
--- /dev/null
+++ b/server/models/externalCommunicationConnector.js
@@ -0,0 +1,110 @@
+const prisma = require("../utils/prisma");
+const { safeJsonParse } = require("../utils/http");
+
+const ExternalCommunicationConnector = {
+ supportedTypes: ["telegram"],
+
+ /**
+ * Get a connector by type.
+ * @param {'telegram'} type
+ * @returns {Promise<{id: number, type: string, config: object, active: boolean}|null>}
+ */
+ get: async function (type) {
+ try {
+ const connector =
+ await prisma.external_communication_connectors.findUnique({
+ where: { type },
+ });
+ if (!connector) return null;
+ return {
+ ...connector,
+ config: safeJsonParse(connector.config, {}),
+ };
+ } catch (error) {
+ console.error("ExternalCommunicationConnector.get", error.message);
+ return null;
+ }
+ },
+
+ /**
+ * Create or update a connector's config and active state.
+ * @param {'telegram'} type
+ * @param {object} config
+ * @param {boolean} active
+ * @returns {Promise<{connector: object|null, error: string|null}>}
+ */
+ upsert: async function (type, config = {}) {
+ if (!this.supportedTypes.includes(type))
+ return { connector: null, error: `Unsupported connector type: ${type}` };
+
+ try {
+ let update = {},
+ create = {};
+
+ if (config.hasOwnProperty("active")) {
+ update.active = Boolean(config.active);
+ create.active = Boolean(config.active);
+ delete config.active;
+ }
+
+ update = Object.assign(update, {
+ config: JSON.stringify(config),
+ lastUpdatedAt: new Date(),
+ });
+ create = Object.assign(create, {
+ config: JSON.stringify(config),
+ type: String(type),
+ });
+
+ const connector = await prisma.external_communication_connectors.upsert({
+ where: { type: String(type) },
+ update,
+ create,
+ });
+ return {
+ connector: {
+ ...connector,
+ config: safeJsonParse(connector.config, {}),
+ },
+ error: null,
+ };
+ } catch (error) {
+ console.error("ExternalCommunicationConnector.upsert", error.message);
+ return { connector: null, error: error.message };
+ }
+ },
+
+ /**
+ * Merge partial config updates into an existing connector.
+ * @param {'telegram'} type
+ * @param {object} configUpdates - Partial config to merge.
+ * @returns {Promise<{connector: object|null, error: string|null}>}
+ */
+ updateConfig: async function (type, configUpdates = {}) {
+ const existing = await this.get(type);
+ if (!existing)
+ return { connector: null, error: `No ${type} connector found` };
+
+ const mergedConfig = { ...existing.config, ...configUpdates };
+ return this.upsert(type, mergedConfig, existing.active);
+ },
+
+ /**
+ * Delete a connector entirely.
+ * @param {'telegram'} type
+ * @returns {Promise}
+ */
+ delete: async function (type) {
+ try {
+ await prisma.external_communication_connectors.delete({
+ where: { type },
+ });
+ return true;
+ } catch (error) {
+ console.error("ExternalCommunicationConnector.delete", error.message);
+ return false;
+ }
+ },
+};
+
+module.exports = { ExternalCommunicationConnector };
diff --git a/server/package.json b/server/package.json
index 7bd533a7..611e9829 100644
--- a/server/package.json
+++ b/server/package.json
@@ -45,6 +45,8 @@
"bcryptjs": "^3.0.3",
"body-parser": "^1.20.3",
"chalk": "^4",
+ "chart.js": "^4.5.1",
+ "chartjs-node-canvas": "^5.0.0",
"check-disk-space": "^3.4.0",
"cheerio": "^1.0.0",
"chromadb": "^2.0.1",
@@ -69,6 +71,7 @@
"mssql": "^10.0.2",
"multer": "2.0.0",
"mysql2": "^3.9.8",
+ "node-telegram-bot-api": "^0.67.0",
"ollama": "^0.6.3",
"openai": "4.95.1",
"pg": "^8.11.5",
diff --git a/server/prisma/migrations/20260319202916_init/migration.sql b/server/prisma/migrations/20260319202916_init/migration.sql
new file mode 100644
index 00000000..a486fbf3
--- /dev/null
+++ b/server/prisma/migrations/20260319202916_init/migration.sql
@@ -0,0 +1,12 @@
+-- CreateTable
+CREATE TABLE "external_communication_connectors" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "type" TEXT NOT NULL,
+ "config" TEXT NOT NULL DEFAULT '{}',
+ "active" BOOLEAN NOT NULL DEFAULT false,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "lastUpdatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "external_communication_connectors_type_key" ON "external_communication_connectors"("type");
diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma
index becdb6d3..326e5f58 100644
--- a/server/prisma/schema.prisma
+++ b/server/prisma/schema.prisma
@@ -385,3 +385,12 @@ model workspace_parsed_files {
@@index([workspaceId])
@@index([userId])
}
+
+model external_communication_connectors {
+ id Int @id @default(autoincrement())
+ type String @unique
+ config String @default("{}")
+ active Boolean @default(false)
+ createdAt DateTime @default(now())
+ lastUpdatedAt DateTime @default(now())
+}
diff --git a/server/utils/BackgroundWorkers/index.js b/server/utils/BackgroundWorkers/index.js
index 6fa43d0f..12535a52 100644
--- a/server/utils/BackgroundWorkers/index.js
+++ b/server/utils/BackgroundWorkers/index.js
@@ -91,6 +91,50 @@ class BackgroundService {
origin: message.name,
});
}
+
+ /**
+ * Run a one-off job via Bree with a data payload sent over IPC.
+ * The job file receives the payload via process.on('message').
+ * @param {string} name - Job filename (without .js) in the jobs directory
+ * @param {object} payload - Data to send to the job via IPC
+ * @param {object} [opts]
+ * @param {function} [opts.onMessage] - Callback for IPC messages from the child process
+ * @returns {Promise} Resolves when the job exits with code 0
+ */
+ async runJob(name, payload = {}, { onMessage } = {}) {
+ const jobId = `${name}-${Date.now()}`;
+
+ await this.bree.add({
+ name: jobId,
+ path: path.resolve(this.#root, `${name}.js`),
+ });
+
+ await this.bree.run(jobId);
+ const worker = this.bree.workers.get(jobId);
+ if (worker && typeof worker.send === "function") {
+ worker.send(payload);
+ }
+ if (worker && onMessage) {
+ worker.on("message", onMessage);
+ }
+
+ return new Promise((resolve, reject) => {
+ worker.on("exit", async (code) => {
+ try {
+ await this.bree.remove(jobId);
+ } catch {}
+ if (code === 0) resolve();
+ else reject(new Error(`Job ${jobId} exited with code ${code}`));
+ });
+
+ worker.on("error", async (err) => {
+ try {
+ await this.bree.remove(jobId);
+ } catch {}
+ reject(err);
+ });
+ });
+ }
}
module.exports.BackgroundService = BackgroundService;
diff --git a/server/utils/EncryptionManager/index.js b/server/utils/EncryptionManager/index.js
index 8ef5619e..6157092c 100644
--- a/server/utils/EncryptionManager/index.js
+++ b/server/utils/EncryptionManager/index.js
@@ -39,7 +39,7 @@ class EncryptionManager {
this.log("Self-assigning key & salt for encrypting arbitrary data.");
process.env[this.#keyENV] = crypto.randomBytes(32).toString("hex");
process.env[this.#saltENV] = crypto.randomBytes(32).toString("hex");
- if (process.env.NODE_ENV === "production") dumpENV();
+ dumpENV();
} else
this.log("Loaded existing key & salt for encrypting arbitrary data.");
diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js
index eaf7040d..7c544855 100644
--- a/server/utils/agents/index.js
+++ b/server/utils/agents/index.js
@@ -5,6 +5,7 @@ const {
} = require("../../models/workspaceAgentInvocation");
const { WorkspaceParsedFiles } = require("../../models/workspaceParsedFiles");
const { User } = require("../../models/user");
+const { Workspace } = require("../../models/workspace");
const { WorkspaceChats } = require("../../models/workspaceChats");
const { safeJsonParse } = require("../http");
const { USER_AGENT, WORKSPACE_AGENT } = require("./defaults");
@@ -36,6 +37,41 @@ class AgentHandler {
this.log(`End ${this.#invocationUUID}::${this.provider}:${this.model}`);
}
+ /**
+ * Determine if the message should invoke the agent handler.
+ * This is true when the user explicitly invokes an agent (via @agent prefix)
+ * or when the workspace is in automatic mode **and** the provider supports native tool calling.
+ * @param {object} parameters
+ * @param {string} parameters.message - The message to check for agent invocation.
+ * @param { import("@prisma/client").workspaces} parameters.workspace - The workspace to check for agent invocation.
+ * @param {string} parameters.chatMode - The chat mode to check for agent invocation.
+ * @returns {Promise}
+ */
+ static async isAgentInvocation({
+ message,
+ workspace = null,
+ chatMode = null,
+ }) {
+ if (this.#isAgentCommandInvocation({ message })) return true;
+ if (chatMode === "automatic") {
+ if (!workspace) return false;
+ if (await Workspace.supportsNativeToolCalling(workspace)) return true;
+ return false;
+ }
+ return false;
+ }
+
+ /**
+ * Determine if the message provided is an agent invocation.
+ * @param {{message:string}} parameters
+ * @returns {boolean}
+ */
+ static #isAgentCommandInvocation({ message }) {
+ const agentHandles = WorkspaceAgentInvocation.parseAgents(message);
+ if (agentHandles.length > 0) return true;
+ return false;
+ }
+
async #chatHistory(limit = 10) {
try {
const rawHistory = (
diff --git a/server/utils/boot/index.js b/server/utils/boot/index.js
index fe69255f..df14154c 100644
--- a/server/utils/boot/index.js
+++ b/server/utils/boot/index.js
@@ -6,6 +6,7 @@ const setupTelemetry = require("../telemetry");
const eagerLoadContextWindows = require("./eagerLoadContextWindows");
const markOnboarded = require("./markOnboarded");
const { PushNotifications } = require("../PushNotifications");
+const { TelegramBotService } = require("../telegramBot");
// Testing SSL? You can make a self signed certificate and point the ENVs to that location
// make a directory in server called 'sslcert' - cd into it
@@ -37,6 +38,7 @@ function bootSSL(app, port = 3001) {
new BackgroundService().boot();
await eagerLoadContextWindows();
await PushNotifications.setupPushNotificationService();
+ await TelegramBotService.bootIfActive();
console.log(`Primary server in HTTPS mode listening on port ${port}`);
})
.on("error", catchSigTerms);
@@ -69,6 +71,7 @@ function bootHTTP(app, port = 3001) {
new BackgroundService().boot();
await eagerLoadContextWindows();
await PushNotifications.setupPushNotificationService();
+ await TelegramBotService.bootIfActive();
console.log(`Primary server in HTTP mode listening on port ${port}`);
})
.on("error", catchSigTerms);
diff --git a/server/utils/middleware/multiUserProtected.js b/server/utils/middleware/multiUserProtected.js
index cf7e58cf..0d89f85f 100644
--- a/server/utils/middleware/multiUserProtected.js
+++ b/server/utils/middleware/multiUserProtected.js
@@ -8,6 +8,18 @@ const ROLES = {
};
const DEFAULT_ROLES = [ROLES.admin, ROLES.admin];
+/**
+ * Explicitly check that single user mode is enabled as well as that the
+ * requesting user has the appropriate role to modify or call the URL.
+ * @returns {function}
+ */
+async function isSingleUserMode(_request, response, next) {
+ const multiUserMode = await SystemSettings.isMultiUserMode();
+ if (multiUserMode) return response.sendStatus(401).end();
+ next();
+ return;
+}
+
/**
* Explicitly check that multi user mode is enabled as well as that the
* requesting user has the appropriate role to modify or call the URL.
@@ -88,6 +100,7 @@ async function isMultiUserSetup(_request, response, next) {
module.exports = {
ROLES,
+ isSingleUserMode,
strictMultiUserRoleValid,
flexUserRoleValid,
isMultiUserSetup,
diff --git a/server/utils/telegramBot/chat/agent.js b/server/utils/telegramBot/chat/agent.js
new file mode 100644
index 00000000..a525df05
--- /dev/null
+++ b/server/utils/telegramBot/chat/agent.js
@@ -0,0 +1,377 @@
+const { v4: uuidv4 } = require("uuid");
+const { EphemeralAgentHandler } = require("../../agents/ephemeral");
+const { WorkspaceChats } = require("../../../models/workspaceChats");
+const { safeJsonParse } = require("../../http");
+const {
+ editMessage,
+ sendFormattedMessage,
+ upsertMessage,
+} = require("../utils");
+const { escapeHTML } = require("../utils/format");
+const { sendVoiceResponse } = require("../utils/media");
+const {
+ STREAM_EDIT_INTERVAL,
+ MAX_MSG_LEN,
+ CURSOR_CHAR,
+} = require("../constants");
+
+const THOUGHT_FLUSH_INTERVAL_MS = 1500;
+
+/**
+ * Run the agent pipeline for @agent messages and send the result to Telegram.
+ * Uses EphemeralAgentHandler to avoid creating a DB invocation record per call.
+ * @param {import("../commands").BotContext} ctx
+ * @param {number} chatId
+ * @param {import('@prisma/client').workspaces} workspace
+ * @param {object|null} thread
+ * @param {string} message
+ * @param {boolean} voiceResponse - Whether to send the response as voice audio
+ * @param {Array<{name: string, mime: string, contentString: string}>} attachments - Image/file attachments for multimodal support
+ */
+async function handleAgentResponse(
+ ctx,
+ chatId,
+ workspace,
+ thread,
+ message,
+ voiceResponse = false,
+ attachments = []
+) {
+ let finalResponse = "";
+ let metrics = {};
+ const sources = [];
+ const thoughts = [];
+ const charts = [];
+ const files = [];
+ let thoughtMsgId = null;
+ let lastThoughtText = "";
+
+ // Streaming response state (similar to stream.js createStreamHandler)
+ let streamingText = "";
+ let responseMsgId = null;
+ let responsePending = null;
+ let lastEditTime = 0;
+ let editTimer = null;
+ let msgOffset = 0;
+
+ const currentResponseText = () => streamingText.slice(msgOffset);
+ const handleStreamChunk = (chunk) => {
+ streamingText += chunk;
+
+ // Handle message splitting when content exceeds Telegram's limit
+ if (responseMsgId !== null && currentResponseText().length > MAX_MSG_LEN) {
+ clearTimeout(editTimer);
+ editTimer = null;
+ editMessage(
+ ctx.bot,
+ chatId,
+ responseMsgId,
+ streamingText.slice(msgOffset, msgOffset + MAX_MSG_LEN),
+ ctx.log,
+ { format: true }
+ ).catch(() => {});
+ msgOffset += MAX_MSG_LEN;
+ responseMsgId = null;
+ responsePending = null;
+ }
+
+ // Send initial message if none exists yet
+ if (responseMsgId === null && !responsePending) {
+ responsePending = ctx.bot
+ .sendMessage(chatId, currentResponseText() + CURSOR_CHAR)
+ .then((sent) => {
+ responseMsgId = sent.message_id;
+ lastEditTime = Date.now();
+ })
+ .catch(() => {
+ responsePending = null;
+ });
+ return;
+ }
+
+ if (!responseMsgId) return;
+
+ // Throttled message updates
+ const now = Date.now();
+ if (now - lastEditTime >= STREAM_EDIT_INTERVAL) {
+ clearTimeout(editTimer);
+ lastEditTime = now;
+ editMessage(
+ ctx.bot,
+ chatId,
+ responseMsgId,
+ currentResponseText() + CURSOR_CHAR,
+ ctx.log
+ ).catch(() => {});
+ } else if (!editTimer) {
+ editTimer = setTimeout(() => {
+ lastEditTime = Date.now();
+ editMessage(
+ ctx.bot,
+ chatId,
+ responseMsgId,
+ currentResponseText() + CURSOR_CHAR,
+ ctx.log
+ ).catch(() => {});
+ editTimer = null;
+ }, STREAM_EDIT_INTERVAL);
+ }
+ };
+
+ const handler = {
+ send(data) {
+ const parsed = safeJsonParse(data, null);
+ if (!parsed) return;
+
+ switch (parsed.type) {
+ case "statusResponse":
+ if (parsed.content) thoughts.push(parsed.content);
+ return;
+ case "rechartVisualize":
+ if (parsed.content) charts.push(parsed.content);
+ return;
+ case "fileDownload":
+ if (parsed.content) files.push(parsed.content);
+ return;
+ case "reportStreamEvent":
+ const inner = parsed.content;
+ if (!inner) return;
+ if (inner.type === "textResponseChunk" && inner.content) {
+ handleStreamChunk(inner.content);
+ return;
+ }
+ if (inner.type === "fullTextResponse" && inner.content) {
+ finalResponse = inner.content;
+ return;
+ }
+ if (inner.type === "usageMetrics" && inner.metrics) {
+ metrics = inner.metrics;
+ return;
+ }
+ if (inner.type === "citations" && inner.citations) {
+ sources.push(...inner.citations);
+ }
+ return;
+ default:
+ if (!parsed.from || parsed.from === "USER" || !parsed.content) return;
+ finalResponse = parsed.content;
+ break;
+ }
+ },
+ close() {},
+ };
+
+ // Periodically flush thoughts as a single live-updating message
+ let flushing = false;
+ let thoughtFlushTimeout = null;
+
+ const formatThoughtsAsBlockquote = (thoughtList, done = false) => {
+ const header = done
+ ? "✓ Agent completed:"
+ : "🤔 Agent is thinking:";
+ const icon = done ? "✓" : "⏳";
+ const content = thoughtList
+ .map((t) => `${icon} ${escapeHTML(t)}`)
+ .join("\n");
+ const fullContent = `${header}\n${content}`;
+ const tag =
+ fullContent.length > 200 ? "blockquote expandable" : "blockquote";
+ return `<${tag}>${fullContent}${tag.split(" ")[0]}>`;
+ };
+
+ const flushThoughts = async () => {
+ if (flushing || thoughts.length === 0) {
+ thoughtFlushTimeout = setTimeout(
+ flushThoughts,
+ THOUGHT_FLUSH_INTERVAL_MS
+ );
+ return;
+ }
+ const text = formatThoughtsAsBlockquote(thoughts, false);
+ if (text === lastThoughtText) {
+ thoughtFlushTimeout = setTimeout(
+ flushThoughts,
+ THOUGHT_FLUSH_INTERVAL_MS
+ );
+ return;
+ }
+ lastThoughtText = text;
+ flushing = true;
+ try {
+ thoughtMsgId = await upsertMessage(
+ ctx.bot,
+ chatId,
+ thoughtMsgId,
+ text,
+ ctx.log,
+ { html: true, disableLinkPreview: true }
+ );
+ } catch (err) {
+ ctx.log?.error?.("Failed to update thought message:", err);
+ } finally {
+ flushing = false;
+ thoughtFlushTimeout = setTimeout(
+ flushThoughts,
+ THOUGHT_FLUSH_INTERVAL_MS
+ );
+ }
+ };
+
+ thoughtFlushTimeout = setTimeout(flushThoughts, THOUGHT_FLUSH_INTERVAL_MS);
+
+ const typingInterval = setInterval(() => {
+ ctx.bot.sendChatAction(chatId, "typing").catch(() => {});
+ }, 4000);
+
+ try {
+ const agentHandler = await new EphemeralAgentHandler({
+ uuid: uuidv4(),
+ workspace,
+ prompt: message,
+ userId: null,
+ threadId: thread?.id || null,
+ attachments,
+ }).init();
+ await agentHandler.createAIbitat({ handler });
+
+ // httpSocket terminates after the first agent message, but cap rounds
+ // as a safety net so the agent can't loop indefinitely.
+ agentHandler.aibitat.maxRounds = 2;
+
+ await agentHandler.startAgentCluster();
+ } finally {
+ clearInterval(typingInterval);
+ clearTimeout(thoughtFlushTimeout);
+ clearTimeout(editTimer);
+ }
+
+ // Final thought update, mark as completed
+ if (thoughtMsgId && thoughts.length > 0) {
+ const doneText = formatThoughtsAsBlockquote(thoughts, true);
+ await editMessage(ctx.bot, chatId, thoughtMsgId, doneText, ctx.log, {
+ html: true,
+ disableLinkPreview: true,
+ });
+ }
+
+ // Send charts as locally rendered images
+ for (const chart of charts) {
+ try {
+ const buffer = await renderChartToBuffer(chart);
+ await ctx.bot.sendPhoto(
+ chatId,
+ buffer,
+ { caption: chart.title },
+ {
+ filename: "chart.png",
+ contentType: "image/png",
+ knownLength: buffer.length,
+ }
+ );
+ } catch {
+ await ctx.bot.sendMessage(
+ chatId,
+ `${chart.title}: failed to render chart.`
+ );
+ }
+ }
+
+ // Send files as Telegram documents
+ for (const file of files) {
+ try {
+ const base64Data = file.b64Content.split(",")[1];
+ const buffer = Buffer.from(base64Data, "base64");
+ await ctx.bot.sendDocument(
+ chatId,
+ buffer,
+ {},
+ {
+ filename: file.filename,
+ contentType: "application/octet-stream",
+ }
+ );
+ } catch {}
+ }
+
+ // Ensure the initial sendMessage has resolved before deciding how to deliver
+ if (responsePending) await responsePending;
+
+ // Fall back to the accumulated streamed text when no explicit
+ // fullTextResponse event was received (e.g. audio/voice messages).
+ const responseText = finalResponse || streamingText;
+
+ if (responseText) {
+ await WorkspaceChats.new({
+ workspaceId: workspace.id,
+ prompt: message,
+ response: {
+ text: responseText,
+ sources,
+ type: "chat",
+ metrics,
+ attachments,
+ },
+ threadId: thread?.id || null,
+ });
+
+ // Always deliver text response first
+ if (responseMsgId) {
+ await editMessage(
+ ctx.bot,
+ chatId,
+ responseMsgId,
+ finalResponse || currentResponseText(),
+ ctx.log,
+ {
+ format: true,
+ }
+ ).catch(() => {});
+ } else {
+ await sendFormattedMessage(ctx.bot, chatId, responseText);
+ }
+
+ // Send voice as an additional attachment if requested
+ if (voiceResponse) {
+ ctx.log?.info?.(`Generating voice response for ${chatId}`);
+ await sendVoiceResponse(ctx.bot, chatId, responseText);
+ }
+ }
+}
+
+/**
+ * Render a chart to a PNG buffer using chartjs-node-canvas.
+ * @param {object} chart - { type, title, dataset }
+ * @returns {Promise}
+ */
+async function renderChartToBuffer(chart) {
+ const { ChartJSNodeCanvas } = require("chartjs-node-canvas");
+ const canvas = new ChartJSNodeCanvas({ width: 600, height: 400 });
+
+ const data = JSON.parse(chart.dataset);
+ const labels = data.map((d) => d.name);
+ const valueKey = Object.keys(data[0]).find((k) => k !== "name");
+ const values = data.map((d) => d[valueKey]);
+
+ const config = {
+ type: chart.type === "area" ? "line" : chart.type,
+ data: {
+ labels,
+ datasets: [
+ {
+ label: chart.title,
+ data: values,
+ fill: chart.type === "area",
+ borderColor: "rgb(59, 130, 246)",
+ backgroundColor: "rgba(59, 130, 246, 0.2)",
+ },
+ ],
+ },
+ options: {
+ plugins: { title: { display: true, text: chart.title } },
+ },
+ };
+
+ return await canvas.renderToBuffer(config);
+}
+
+module.exports = { handleAgentResponse };
diff --git a/server/utils/telegramBot/chat/stream.js b/server/utils/telegramBot/chat/stream.js
new file mode 100644
index 00000000..5be46fde
--- /dev/null
+++ b/server/utils/telegramBot/chat/stream.js
@@ -0,0 +1,469 @@
+const { WorkspaceChats } = require("../../../models/workspaceChats");
+const { getLLMProvider, getVectorDbClass } = require("../../helpers");
+const { DocumentManager } = require("../../DocumentManager");
+const {
+ sourceIdentifier,
+ recentChatHistory,
+ chatPrompt,
+} = require("../../chats");
+const { fillSourceWindow } = require("../../helpers/chat");
+const { AgentHandler } = require("../../agents");
+const {
+ STREAM_EDIT_INTERVAL,
+ MAX_MSG_LEN,
+ CURSOR_CHAR,
+} = require("../constants");
+const { editMessage, sendFormattedMessage } = require("../utils");
+const { sendVoiceResponse } = require("../utils/media");
+const { safeJsonParse } = require("../../http");
+const { handleAgentResponse } = require("./agent");
+
+/**
+ * Check if the history is agentic by checking if any user messages start with "@agent"
+ * so that "chat" mode workspaces can still carry on with agentic conversations
+ * otherwise this is handled with "automatic" mode.
+ * @param {'chat' | 'automatic' | 'query'} chatMode - The chat mode.
+ * @param {{role: 'user' | 'assistant', content: string}[]} chatHistory - The chat history.
+ * @returns {boolean} - True if the history is agentic, false otherwise.
+ */
+function historyIsAgentic(chatMode, chatHistory) {
+ if (chatMode !== "chat") return false;
+ return chatHistory.some(
+ (message) => message.role === "user" && message.content.startsWith("@agent")
+ );
+}
+
+/**
+ * Stream a response to Telegram by running the full RAG pipeline.
+ * Uses the same pipeline as the web UI (RAG, parsed docs, pinned docs, etc.)
+ * and stores chats with thread_id so they appear in the AnythingLLM UI.
+ *
+ * However, we are able to consistently handle agentic conversations in "chat" mode by checking the chat history
+ * without needing to open/close an agent invocation every chat which is wasteful on the DB.
+ *
+ * Query mode is also not supported in this flow - as it would be pretty useless.
+ *
+ * @param {object} context - The context object.
+ * @param {import("../commands").BotContext} context.ctx - The bot object.
+ * @param {number} context.chatId - The chat ID.
+ * @param {import('@prisma/client').workspaces} context.workspace - The workspace object.
+ * @param {object|null} context.thread - The thread object.
+ * @param {string} context.message - The message to send.
+ * @param {array} context.attachments - The attachments to send.
+ * @param {boolean} context.voiceResponse - Whether to send the response as voice.
+ */
+async function streamResponse({
+ ctx = null,
+ chatId = null,
+ workspace = null,
+ thread = null,
+ message = "",
+ attachments = [],
+ voiceResponse = false,
+}) {
+ if (!ctx?.bot || !chatId || !workspace || !message)
+ throw new Error("Invalid context or missing required parameters!");
+
+ await ctx.bot.sendChatAction(chatId, "typing");
+
+ const chatMode = workspace.chatMode || "chat";
+ const messageLimit = workspace?.openAiHistory || 20;
+ const { rawHistory, chatHistory } = await recentChatHistory({
+ workspace,
+ thread,
+ messageLimit,
+ });
+
+ if (
+ historyIsAgentic(chatMode, chatHistory) ||
+ (await AgentHandler.isAgentInvocation({
+ message,
+ workspace,
+ chatMode: workspace.chatMode ?? "chat",
+ }))
+ ) {
+ return await handleAgentResponse(
+ ctx,
+ chatId,
+ workspace,
+ thread,
+ message,
+ voiceResponse,
+ attachments
+ );
+ }
+
+ const typingInterval = setInterval(() => {
+ ctx.bot.sendChatAction(chatId, "typing").catch(() => {});
+ }, 4000);
+
+ const LLMConnector = getLLMProvider({
+ provider: workspace?.chatProvider,
+ model: workspace?.chatModel,
+ });
+ const VectorDb = getVectorDbClass();
+ const embeddingsCount = await VectorDb.namespaceCount(workspace.slug);
+
+ const {
+ contextTexts: pinnedContextTexts,
+ sources: pinnedSources,
+ pinnedDocIdentifiers,
+ } = await collectPinnedDocs(workspace, LLMConnector);
+
+ const {
+ contextTexts: searchContextTexts,
+ sources: searchSources,
+ error: searchError,
+ } = await buildSearchContext({
+ workspace,
+ message,
+ VectorDb,
+ LLMConnector,
+ embeddingsCount,
+ rawHistory,
+ pinnedDocIdentifiers,
+ });
+
+ if (searchError) {
+ clearInterval(typingInterval);
+ return await ctx.bot.sendMessage(chatId, searchError);
+ }
+
+ const contextTexts = [...pinnedContextTexts, ...searchContextTexts];
+ const sources = [...pinnedSources, ...searchSources];
+ const messages = await LLMConnector.compressMessages(
+ {
+ systemPrompt: await chatPrompt(workspace),
+ userPrompt: message,
+ contextTexts,
+ chatHistory,
+ attachments,
+ },
+ rawHistory
+ );
+
+ try {
+ const { completeText, metrics } = await generateResponse({
+ LLMConnector,
+ messages,
+ workspace,
+ ctx,
+ chatId,
+ });
+
+ await persistAndDeliver({
+ workspace,
+ thread,
+ message,
+ completeText,
+ sources,
+ chatMode,
+ metrics,
+ attachments,
+ voiceResponse,
+ ctx,
+ chatId,
+ });
+ } catch (error) {
+ console.error("Error streaming response:", error);
+ await ctx.bot.sendMessage(
+ chatId,
+ "An error occurred while streaming the response."
+ );
+ } finally {
+ clearInterval(typingInterval);
+ }
+}
+
+/**
+ * Gather context texts, sources, and identifiers from pinned documents.
+ * @returns {Promise<{ contextTexts: string[], sources: object[], pinnedDocIdentifiers: string[] }>}
+ */
+async function collectPinnedDocs(workspace, LLMConnector) {
+ const contextTexts = [];
+ const sources = [];
+ const pinnedDocIdentifiers = [];
+
+ const pinnedDocs = await new DocumentManager({
+ workspace,
+ maxTokens: LLMConnector.promptWindowLimit(),
+ }).pinnedDocs();
+
+ for (const doc of pinnedDocs) {
+ const { pageContent, ...metadata } = doc;
+ pinnedDocIdentifiers.push(sourceIdentifier(doc));
+ contextTexts.push(pageContent);
+ sources.push({
+ text:
+ pageContent.slice(0, 1_000) + "...continued on in source document...",
+ ...metadata,
+ });
+ }
+
+ return { contextTexts, sources, pinnedDocIdentifiers };
+}
+
+/**
+ * Run vector similarity search and fill the source window.
+ * @returns {Promise<{ contextTexts: string[], sources: object[], error: string|null }>}
+ */
+async function buildSearchContext({
+ workspace,
+ message,
+ VectorDb,
+ LLMConnector,
+ embeddingsCount,
+ rawHistory,
+ pinnedDocIdentifiers,
+}) {
+ const vectorSearchResults =
+ embeddingsCount !== 0
+ ? await VectorDb.performSimilaritySearch({
+ namespace: workspace.slug,
+ input: message,
+ LLMConnector,
+ similarityThreshold: workspace?.similarityThreshold,
+ topN: workspace?.topN,
+ filterIdentifiers: pinnedDocIdentifiers,
+ rerank: workspace?.vectorSearchMode === "rerank",
+ })
+ : { contextTexts: [], sources: [], message: null };
+
+ if (vectorSearchResults.message) {
+ return {
+ contextTexts: [],
+ sources: [],
+ error: "Vector search failed. Please try again.",
+ };
+ }
+
+ const filledSources = fillSourceWindow({
+ nDocs: workspace?.topN || 4,
+ searchResults: vectorSearchResults.sources,
+ history: rawHistory,
+ filterIdentifiers: pinnedDocIdentifiers,
+ });
+
+ return {
+ contextTexts: filledSources.contextTexts,
+ sources: vectorSearchResults.sources,
+ error: null,
+ };
+}
+
+/**
+ * Run the LLM completion (streaming or non-streaming) and deliver the in-progress response.
+ * Clears the typing indicator when done.
+ * @returns {Promise<{ completeText: string, metrics: object }>}
+ */
+async function generateResponse({
+ LLMConnector,
+ messages,
+ workspace,
+ ctx,
+ chatId,
+}) {
+ let completeText = "";
+ let metrics = {};
+
+ if (LLMConnector.streamingEnabled() === true) {
+ const stream = await LLMConnector.streamGetChatCompletion(messages, {
+ temperature: workspace?.openAiTemp ?? LLMConnector.defaultTemp,
+ });
+
+ const { responseHandler, flushEdit } = createStreamHandler({
+ ctx,
+ chatId,
+ });
+
+ completeText = await LLMConnector.handleStream(responseHandler, stream, {
+ uuid: chatId.toString(),
+ });
+
+ await flushEdit(true);
+ metrics = stream.metrics || {};
+ } else {
+ const { textResponse, metrics: performanceMetrics } =
+ await LLMConnector.getChatCompletion(messages, {
+ temperature: workspace?.openAiTemp ?? LLMConnector.defaultTemp,
+ user: null,
+ });
+ completeText = textResponse;
+ metrics = performanceMetrics || {};
+ if (completeText?.length > 0)
+ await sendFormattedMessage(ctx.bot, chatId, completeText);
+ }
+
+ return { completeText, metrics };
+}
+
+/**
+ * Save the completed chat to the database and optionally deliver a voice response.
+ */
+async function persistAndDeliver({
+ workspace,
+ thread,
+ message,
+ completeText,
+ sources,
+ chatMode,
+ metrics,
+ attachments,
+ voiceResponse,
+ ctx,
+ chatId,
+}) {
+ if (!completeText?.length) {
+ await ctx.bot.sendMessage(chatId, "No response generated.");
+ return;
+ }
+
+ await WorkspaceChats.new({
+ workspaceId: workspace.id,
+ prompt: message,
+ response: {
+ text: completeText,
+ sources,
+ type: chatMode,
+ metrics,
+ attachments,
+ },
+ threadId: thread?.id || null,
+ });
+
+ // Send voice as an additional attachment if requested
+ if (voiceResponse) {
+ ctx.log?.info?.(`Generating voice response for ${chatId}`);
+ await sendVoiceResponse(ctx.bot, chatId, completeText);
+ }
+}
+
+/**
+ * Parse an SSE data chunk and return the text token, or null if not a text token.
+ */
+function parseSSEChunk(data) {
+ const match = data.match(/^data: (.+)\n\n$/s);
+ if (!match) return null;
+ const parsed = safeJsonParse(match[1], null);
+ if (!parsed || !parsed.textResponse || parsed.close) return null;
+ return parsed.textResponse;
+}
+
+/**
+ * Create a stream response handler for editing Telegram messages as tokens arrive.
+ * Manages message splitting when content exceeds Telegram's length limit.
+ * @param {object} options
+ * @param {import("./commands").BotContext} options.ctx - Bot context
+ * @param {number} options.chatId - Telegram chat ID
+ * @returns {{ responseHandler: object, flushEdit: function }}
+ */
+function createStreamHandler({ ctx, chatId }) {
+ let completeText = "";
+ let messageId = null;
+ let messagePending = null;
+ let lastEditTime = 0;
+ let editTimer = null;
+ let msgOffset = 0;
+
+ const currentText = () => completeText.slice(msgOffset);
+
+ /**
+ * Finalize the current message and reset state when accumulated text
+ * exceeds Telegram's max message length.
+ */
+ function splitMessageIfOverflow() {
+ if (messageId === null || currentText().length <= MAX_MSG_LEN) return;
+ clearTimeout(editTimer);
+ editTimer = null;
+ editMessage(
+ ctx.bot,
+ chatId,
+ messageId,
+ completeText.slice(msgOffset, msgOffset + MAX_MSG_LEN),
+ ctx.log,
+ { format: true }
+ ).catch(() => {});
+ msgOffset += MAX_MSG_LEN;
+ messageId = null;
+ messagePending = null;
+ }
+
+ /**
+ * Send a new Telegram message when none exists yet.
+ * @returns {boolean} true if a new message was initiated (caller should skip edit).
+ */
+ function startNewMessageIfNeeded() {
+ if (messageId !== null || messagePending) return false;
+ messagePending = ctx.bot
+ .sendMessage(chatId, currentText() + CURSOR_CHAR)
+ .then((sent) => {
+ messageId = sent.message_id;
+ lastEditTime = Date.now();
+ })
+ .catch(() => {
+ messagePending = null;
+ });
+ return true;
+ }
+
+ /**
+ * Throttle edits to the current message so we don't exceed Telegram rate limits.
+ */
+ function scheduleThrottledEdit() {
+ if (!messageId) return;
+
+ const now = Date.now();
+ if (now - lastEditTime >= STREAM_EDIT_INTERVAL) {
+ clearTimeout(editTimer);
+ lastEditTime = now;
+ editMessage(
+ ctx.bot,
+ chatId,
+ messageId,
+ currentText() + CURSOR_CHAR,
+ ctx.log
+ ).catch(() => {});
+ } else if (!editTimer) {
+ editTimer = setTimeout(() => {
+ lastEditTime = Date.now();
+ editMessage(
+ ctx.bot,
+ chatId,
+ messageId,
+ currentText() + CURSOR_CHAR,
+ ctx.log
+ ).catch(() => {});
+ editTimer = null;
+ }, STREAM_EDIT_INTERVAL);
+ }
+ }
+
+ const flushEdit = async (final = false) => {
+ if (messagePending) await messagePending;
+ if (!messageId) return;
+ clearTimeout(editTimer);
+ editTimer = null;
+ const text = currentText();
+ const display = final ? text : text + CURSOR_CHAR;
+ await editMessage(ctx.bot, chatId, messageId, display, ctx.log, {
+ format: final,
+ }).catch(() => {});
+ };
+
+ const responseHandler = {
+ on: () => {},
+ removeListener: () => {},
+ write: (data) => {
+ const token = parseSSEChunk(data);
+ if (!token) return;
+
+ completeText += token;
+ splitMessageIfOverflow();
+ if (!startNewMessageIfNeeded()) scheduleThrottledEdit();
+ },
+ };
+
+ return { responseHandler, flushEdit };
+}
+
+module.exports = { streamResponse };
diff --git a/server/utils/telegramBot/constants.js b/server/utils/telegramBot/constants.js
new file mode 100644
index 00000000..e744c009
--- /dev/null
+++ b/server/utils/telegramBot/constants.js
@@ -0,0 +1,22 @@
+/**
+ * Minimum interval between Telegram message edits (ms) to avoid rate limiting
+ */
+const STREAM_EDIT_INTERVAL = 600;
+
+/**
+ * Telegram messages cap at 4096 chars. We use 4000 to leave headroom
+ * so we can finalize the current message and continue in a new one.
+ */
+const MAX_MSG_LEN = 4000;
+
+/**
+ * The cursor character to use for streaming responses.
+ * Looks like a blinking block, but doesnt actually blink.
+ */
+const CURSOR_CHAR = " \u258d";
+
+module.exports = {
+ STREAM_EDIT_INTERVAL,
+ MAX_MSG_LEN,
+ CURSOR_CHAR,
+};
diff --git a/server/utils/telegramBot/index.js b/server/utils/telegramBot/index.js
new file mode 100644
index 00000000..75620174
--- /dev/null
+++ b/server/utils/telegramBot/index.js
@@ -0,0 +1,632 @@
+// Suppress deprecated content-type warning when sending files via the Telegram bot API.
+// https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+process.env.NTBA_FIX_350 = 1;
+const TelegramBot = require("node-telegram-bot-api");
+const {
+ ExternalCommunicationConnector,
+} = require("../../models/externalCommunicationConnector");
+const { BackgroundService } = require("../BackgroundWorkers");
+const { MessageQueue } = require("./utils/messageQueue");
+const { decryptToken } = require("./utils");
+const {
+ WorkspaceAgentInvocation,
+} = require("../../models/workspaceAgentInvocation");
+const {
+ isVerified,
+ sendPairingRequest,
+ approveUser,
+ denyUser,
+ revokeUser,
+} = require("./utils/verification");
+const { BOT_COMMANDS } = require("./utils/commands");
+const { handleKeyboardQueryCallback } = require("./utils/navigation");
+const {
+ downloadTelegramFile,
+ transcribeAudio,
+ documentToText,
+ photoToAttachment,
+} = require("./utils/media");
+
+class TelegramBotService {
+ static _instance = null;
+ #bot = null;
+ #config = null;
+ #queue = new MessageQueue();
+ // Per-chat state: { workspaceSlug, threadSlug }
+ #chatState = new Map();
+ // Pending pairing requests: chatId -> { code, telegramUsername, firstName }
+ #pendingPairings = new Map();
+ // Active workers per chat: chatId -> { worker, jobId }
+ #activeWorkers = new Map();
+
+ constructor() {
+ if (TelegramBotService._instance) return TelegramBotService._instance;
+ TelegramBotService._instance = this;
+ }
+
+ get isRunning() {
+ return this.#bot !== null;
+ }
+
+ get pendingPairings() {
+ const pairings = [];
+ for (const [chatId, data] of this.#pendingPairings) {
+ pairings.push({ chatId: String(chatId), ...data });
+ }
+ return pairings;
+ }
+
+ #log(text, ...args) {
+ console.log(`\x1b[35m[TelegramBot]\x1b[0m ${text}`, ...args);
+ }
+
+ async start(config) {
+ if (this.#bot) await this.stop();
+ this.#config = config;
+
+ // Clear pending updates on startup, keeping only the last message per chat
+ // This prevents processing a backlog of messages when the bot restarts.
+ this.#bot = new TelegramBot(config.bot_token, { polling: false });
+ const lastMessages = await this.#clearPendingUpdates();
+ this.#bot.startPolling();
+
+ // Restore per-user workspace/thread state from saved config
+ for (const user of config.approved_users || []) {
+ if (user.active_workspace) {
+ this.#chatState.set(Number(user.chatId), {
+ workspaceSlug: user.active_workspace,
+ threadSlug: user.active_thread || null,
+ });
+ }
+ }
+
+ this.#setupHandlers();
+ await this.#registerCommands();
+ this.#log(`Started polling as @${config.bot_username || "unknown"}`);
+
+ // Process only the last message from each chat that was pending
+ if (lastMessages.size > 0) {
+ this.#log(
+ `Processing ${lastMessages.size} pending message(s) from startup`
+ );
+ const ctx = this.#createContext();
+ for (const [chatId, msg] of lastMessages) {
+ if (!isVerified(this.#config.approved_users, chatId)) continue;
+ this.#processPendingMessage(ctx, msg);
+ }
+ }
+ }
+
+ /**
+ * Process a single pending message from startup.
+ * Handles both commands and regular messages.
+ */
+ #processPendingMessage(ctx, msg) {
+ const text = msg.text || "";
+
+ // Handle commands
+ if (text.startsWith("/")) {
+ const commandMatch = text.match(/^\/(\w+)/);
+ if (!commandMatch) return;
+
+ const commandName = commandMatch[1];
+ const command = BOT_COMMANDS.find((c) => c.command === commandName);
+ if (command) {
+ const handler = command.initHandler();
+ handler(ctx, msg.chat.id, text);
+ return;
+ }
+ }
+
+ // Handle regular messages
+ this.#handleMessage(ctx, msg);
+ }
+
+ /**
+ * Check if the instance is running in multi-user mode
+ * @returns {Promise}
+ */
+ async checkMultiUserMode() {
+ const { SystemSettings } = require("../../models/systemSettings");
+ return await SystemSettings.isMultiUserMode();
+ }
+
+ updateConfig(updates) {
+ if (!this.#config) return;
+ Object.assign(this.#config, updates);
+ }
+
+ async stop() {
+ if (!this.#bot) return;
+ try {
+ await this.#bot.stopPolling();
+ } catch {
+ // Polling may already be stopped
+ }
+ // Kill any active workers before clearing state
+ for (const chatId of this.#activeWorkers.keys()) {
+ this.abortChat(chatId);
+ }
+ this.#bot = null;
+ this.#config = null;
+ this.#queue.clear();
+ this.#chatState.clear();
+ this.#pendingPairings.clear();
+ this.#activeWorkers.clear();
+ this.#log("Stopped");
+ }
+
+ /**
+ * Self-cleanup when the bot token becomes invalid (e.g., bot deleted).
+ * Stops polling and removes the connector from the database.
+ */
+ async #selfCleanup(reason) {
+ this.#log(`Self-cleanup triggered: ${reason}`);
+ await this.stop();
+ await ExternalCommunicationConnector.delete("telegram");
+ this.#log("Connector deleted due to invalid token");
+ }
+
+ /**
+ * Handle polling errors with special handling for 401 Unauthorized.
+ * - 401 errors: Self-cleanup and delete connector
+ * - Other HTTP error codes: Stop polling immediately
+ */
+ async #handlePollingError(error) {
+ this.#log("Polling error:", error.message);
+ if (error.message?.includes("401")) {
+ this.#log(
+ "Got 401 - bot token may be invalid. Stopping polling and deleting connector."
+ );
+ return this.#selfCleanup("401 Unauthorized");
+ }
+
+ this.#log(
+ `Got HTTP error ${error.message}. Stopping polling to prevent further errors.`
+ );
+ return this.stop();
+ }
+
+ /**
+ * Clear pending updates on startup, keeping only the last user message per chat.
+ * This prevents processing a backlog of messages when the bot restarts.
+ * @returns {Promise