From cec67d77f2779b184be9cd74a468fcd7da1b6f70 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Fri, 10 Apr 2026 14:14:12 -0700 Subject: [PATCH] GMail Agent Skill (#5400) * wip * remove label tech * ask to read attachments * update skills * Skill ready and tested * report dynamic citations and generic get mailbox util * norm translations * translations * remove dead code, remove connector in multiUser * simple refactor - dont ask for drafts * refactor filesize helper * norm translations, remove read_messages skill --- frontend/src/locales/ar/common.js | 121 ++++ frontend/src/locales/cs/common.js | 120 ++++ frontend/src/locales/da/common.js | 119 ++++ frontend/src/locales/de/common.js | 125 ++++ frontend/src/locales/en/common.js | 116 ++++ frontend/src/locales/es/common.js | 127 ++++ frontend/src/locales/et/common.js | 117 ++++ frontend/src/locales/fa/common.js | 118 ++++ frontend/src/locales/fr/common.js | 126 ++++ frontend/src/locales/he/common.js | 117 ++++ frontend/src/locales/it/common.js | 126 ++++ frontend/src/locales/ja/common.js | 115 ++++ frontend/src/locales/ko/common.js | 114 ++++ frontend/src/locales/lt/common.js | 120 ++++ frontend/src/locales/lv/common.js | 122 ++++ frontend/src/locales/nl/common.js | 120 ++++ frontend/src/locales/pl/common.js | 125 ++++ frontend/src/locales/pt_BR/common.js | 124 ++++ frontend/src/locales/ro/common.js | 123 ++++ frontend/src/locales/ru/common.js | 124 ++++ frontend/src/locales/tr/common.js | 125 ++++ frontend/src/locales/vn/common.js | 119 ++++ frontend/src/locales/zh/common.js | 112 ++++ frontend/src/locales/zh_TW/common.js | 112 ++++ .../Admin/Agents/GMailSkillPanel/gmail.png | Bin 0 -> 21933 bytes .../Admin/Agents/GMailSkillPanel/index.jsx | 443 ++++++++++++++ .../Admin/Agents/GMailSkillPanel/utils.js | 168 ++++++ frontend/src/pages/Admin/Agents/index.jsx | 17 +- frontend/src/pages/Admin/Agents/skills.js | 10 + server/endpoints/admin.js | 9 + server/models/systemSettings.js | 33 ++ .../gmail/account/gmail-get-mailbox-stats.js | 75 +++ .../gmail/drafts/gmail-create-draft-reply.js | 220 +++++++ .../gmail/drafts/gmail-create-draft.js | 217 +++++++ .../gmail/drafts/gmail-delete-draft.js | 87 +++ .../plugins/gmail/drafts/gmail-get-draft.js | 84 +++ .../plugins/gmail/drafts/gmail-list-drafts.js | 96 +++ .../plugins/gmail/drafts/gmail-send-draft.js | 94 +++ .../gmail/drafts/gmail-update-draft.js | 217 +++++++ .../agents/aibitat/plugins/gmail/index.js | 73 +++ .../utils/agents/aibitat/plugins/gmail/lib.js | 555 ++++++++++++++++++ .../plugins/gmail/search/gmail-get-inbox.js | 104 ++++ .../plugins/gmail/search/gmail-read-thread.js | 114 ++++ .../plugins/gmail/search/gmail-search.js | 121 ++++ .../gmail/send/gmail-reply-to-thread.js | 238 ++++++++ .../plugins/gmail/send/gmail-send-email.js | 242 ++++++++ .../plugins/gmail/threads/gmail-mark-read.js | 87 +++ .../gmail/threads/gmail-mark-unread.js | 87 +++ .../gmail/threads/gmail-move-to-archive.js | 89 +++ .../gmail/threads/gmail-move-to-inbox.js | 89 +++ .../gmail/threads/gmail-move-to-trash.js | 89 +++ server/utils/agents/aibitat/plugins/index.js | 3 + server/utils/agents/defaults.js | 35 +- 53 files changed, 6578 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/Admin/Agents/GMailSkillPanel/gmail.png create mode 100644 frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx create mode 100644 frontend/src/pages/Admin/Agents/GMailSkillPanel/utils.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/account/gmail-get-mailbox-stats.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft-reply.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-delete-draft.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-get-draft.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-list-drafts.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-send-draft.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/drafts/gmail-update-draft.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/index.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/lib.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/search/gmail-get-inbox.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/search/gmail-read-thread.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/search/gmail-search.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/send/gmail-reply-to-thread.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/send/gmail-send-email.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-read.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-unread.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-archive.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-inbox.js create mode 100644 server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-trash.js diff --git a/frontend/src/locales/ar/common.js b/frontend/src/locales/ar/common.js index 4770984a..77b9d9ed 100644 --- a/frontend/src/locales/ar/common.js +++ b/frontend/src/locales/ar/common.js @@ -407,6 +407,127 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "موصل Gmail", + description: + "اسمح لمسؤولك بالتفاعل مع Gmail - للبحث عن رسائل البريد الإلكتروني، وقراءة المحادثات، وكتابة المسودات، وإرسال رسائل البريد الإلكتروني، وإدارة صندوق الوارد الخاص بك. اقرأ الوثائق.", + multiUserWarning: + "لا تتوفر تكامل Gmail في الوضع متعدد المستخدمين لأسباب تتعلق بالأمان. يرجى تعطيل الوضع متعدد المستخدمين لاستخدام هذه الميزة.", + configuration: "إعدادات Gmail", + deploymentId: "رقم التوزيع", + deploymentIdHelp: + "معرّف النشر من تطبيق الويب الخاص بك في Google Apps Script", + apiKey: "مفتاح واجهة برمجة التطبيقات", + apiKeyHelp: + "مفتاح واجهة برمجة التطبيقات (API) الذي قمت بتكوينه في تطبيق Google Apps Script الخاص بك.", + configurationRequired: + "يرجى تكوين مُعرّف النشر والمفتاح الخاص لواجهة برمجة التطبيقات لتمكين ميزات Gmail.", + configured: "تم التكوين", + searchSkills: "مهارات البحث...", + noSkillsFound: "لا توجد مهارات تتطابق مع بحثك.", + categories: { + search: { + title: "البحث عن الرسائل وقراءتها", + description: + "ابحث و اقرأ رسائل البريد الإلكتروني من صندوق بريدك الوارد في Gmail.", + }, + drafts: { + title: "نماذج رسائل بريد إلكتروني", + description: "إنشاء، وتحرير، وإدارة مسودات الرسائل الإلكترونية.", + }, + send: { + title: "إرسال واستقبال الرسائل الإلكترونية", + description: "أرسل رسائل بريد إلكتروني واستجب للمناقشات على الفور.", + }, + threads: { + title: "إدارة سلاسل رسائل البريد الإلكتروني", + description: + 'إدارة سلاسل رسائل البريد الإلكتروني: وضع علامة "قرأ/لم يقرأ"، والاحتفاظ بها، وحذفها.', + }, + account: { + title: "إحصائيات التكامل", + description: "عرض إحصائيات صندوق البريد ومعلومات الحساب.", + }, + }, + skills: { + search: { + title: "البحث في رسائل البريد الإلكتروني", + description: + "ابحث في رسائل البريد الإلكتروني باستخدام صيغة استعلام Gmail.", + }, + readThread: { + title: "اقرأ الموضوع", + description: + "اقرأ سلسلة رسائل بريد إلكتروني كاملة من خلال معرف المستخدم (ID).", + }, + createDraft: { + title: "إنشاء مسودة", + description: "إنشاء رسالة بريد إلكتروني جديدة.", + }, + createDraftReply: { + title: "إنشاء رد مقترح", + description: "قم بإنشاء رد مقترح على موضوع قائم.", + }, + updateDraft: { + title: "تحديث المسودة", + description: "تحديث رسالة بريد إلكتروني مسودة موجودة.", + }, + getDraft: { + title: "الحصول على النسخة الأولية", + description: "استرجاع مسودة محددة باستخدام رقم التعريف.", + }, + listDrafts: { + title: "قائمة المسودات", + description: "اعرض جميع رسائل البريد الإلكتروني المقترحة.", + }, + deleteDraft: { + title: "حذف المسودة", + description: "حذف رسالة بريد إلكتروني مسودة.", + }, + sendDraft: { + title: "إرسال مسودة", + description: "أرسل نسخة من رسالة بريد إلكتروني موجودة.", + }, + sendEmail: { + title: "إرسال بريد إلكتروني", + description: "أرسل بريدًا إلكترونيًا على الفور.", + }, + replyToThread: { + title: "الرد على الموضوع", + description: "الرد على سلسلة رسائل البريد الإلكتروني على الفور.", + }, + markRead: { + title: "مارك ريد", + description: "ضع علامة على الموضوع بأنه تمت قراءته.", + }, + markUnread: { + title: 'وضع علامة "لم يقرأ"', + description: 'ضع علامة على الموضوع ليكون "غير مقروء".', + }, + moveToTrash: { + title: "إرسال إلى سلة المهملات", + description: "نقل موضوع إلى سلة المهملات", + }, + moveToArchive: { + title: "أرشيف", + description: "أرشفة الموضوع", + }, + moveToInbox: { + title: "انتقل إلى صندوق الوارد", + description: "انقل موضوعًا إلى صندوق الوارد", + }, + getMailboxStats: { + title: "إحصائيات صندوق البريد", + description: + "احصل على عدد الرسائل غير المقروء وإحصائيات صندوق البريد.", + }, + getInbox: { + title: "الوصول إلى صندوق الوارد", + description: + "طريقة مبسطة للحصول على رسائل البريد الإلكتروني الواردة من حساب Gmail.", + }, + }, + }, }, mcp: { title: "خوادم نظام MCP", diff --git a/frontend/src/locales/cs/common.js b/frontend/src/locales/cs/common.js index 3016c030..dcecaabd 100644 --- a/frontend/src/locales/cs/common.js +++ b/frontend/src/locales/cs/common.js @@ -424,6 +424,126 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Připojení k GMail", + description: + "Umožněte svému agentovi, aby interagoval s Gmail – vyhledával e-maily, četl konverzace, vytvářel návrhy, posílal e-maily a spravoval vaši schránku. Prostudujte dokumentaci.", + multiUserWarning: + "Integrace s Gmailem není dostupná v režimu pro více uživatelů z bezpečnostních důvodů. Pro použití této funkce, prosím, deaktivujte režim pro více uživatelů.", + configuration: "Konfigurace Gmailu", + deploymentId: "ID nasazení", + deploymentIdHelp: + "Identifikátor nasazení z vaší webové aplikace Google Apps Script", + apiKey: "Klíč API", + apiKeyHelp: + "Klíč API, který jste nakonfigurovali ve vaší instalaci Google Apps Script", + configurationRequired: + "Prosím, nakonfigurujte ID nasazení a klíč API, abyste aktivovali funkce pro Gmail.", + configured: "Nastaveno", + searchSkills: "Dovednosti pro vyhledávání...", + noSkillsFound: "Žádný z nabízených profilů neodpovídá vašim kritériím.", + categories: { + search: { + title: "Vyhledávání a čtení e-mailů", + description: "Vyhledejte a čtěte e-maily z vaší schránky Gmail.", + }, + drafts: { + title: "Návrhy e-mailů", + description: "Vytvářejte, upravujte a spravujte návrhy e-mailů.", + }, + send: { + title: "Odesílejte a odpovídejte na e-maily", + description: + "Odesílejte e-maily a okamžitě odpovídejte na diskuse.", + }, + threads: { + title: "Spravujte emailové vlákna", + description: + "Spravujte e-mailové vlákna – označte jako přečtené/ne přečtené, archivujte, vyhoďte", + }, + account: { + title: "Statistiky integrace", + description: + "Zobrazte statistiky poštovní schránky a informace o účtu.", + }, + }, + skills: { + search: { + title: "Hledat e-maily", + description: "Hledejte e-maily pomocí syntaxe dotazů v Gmailu", + }, + readThread: { + title: "Přečtěte si vlákno", + description: "Přečtěte si kompletní řetězec e-mailů podle ID.", + }, + createDraft: { + title: "Vytvořit návrh", + description: "Vytvořte nový návrh e-mailu", + }, + createDraftReply: { + title: "Vytvořit návrh odpovědi", + description: "Vytvořte návrh odpovědi na existující téma.", + }, + updateDraft: { + title: "Aktualizovaná verze návrhu", + description: "Aktualizujte existující návrh e-mailu", + }, + getDraft: { + title: "Získej návrh", + description: "Získejte konkrétní verzi dokumentu podle jejího ID.", + }, + listDrafts: { + title: "Návrhy (seznam)", + description: "Vypište všechny návrhy e-mailů.", + }, + deleteDraft: { + title: "Smazat návrh", + description: "Smazat návrh e-mailu", + }, + sendDraft: { + title: "Odešlete návrh", + description: "Odešlete existující návrh e-mailu", + }, + sendEmail: { + title: "Odeslat e-mail", + description: "Odešlete e-mail co nejrychleji.", + }, + replyToThread: { + title: "Odpovědět na vlákno", + description: "Odpovězte na e-mailovou diskuzi co nejdříve.", + }, + markRead: { + title: "Mark Read", + description: "Označit vlákno jako přečtené", + }, + markUnread: { + title: "Označit jako nepročtené", + description: "Označte vlákno jako nepročtené.", + }, + moveToTrash: { + title: "Přesun do koše", + description: "Přesuňte vlákno do koše", + }, + moveToArchive: { + title: "Archiv", + description: "Uložte vlákno do archivu", + }, + moveToInbox: { + title: "Přesun do schránky", + description: "Přesuňte vlákno do schránky (účetní knihy)", + }, + getMailboxStats: { + title: "Statistiky poštovní schránky", + description: + "Získejte informace o počtu nečtených e-mailů a statistiky poštovní schránky.", + }, + getInbox: { + title: "Otevřít schránku", + description: + "Jednoduchý způsob, jak získat e-maily z vaší schránky Gmail.", + }, + }, + }, }, mcp: { title: "Servery společnosti MCP", diff --git a/frontend/src/locales/da/common.js b/frontend/src/locales/da/common.js index 51712327..9af72df4 100644 --- a/frontend/src/locales/da/common.js +++ b/frontend/src/locales/da/common.js @@ -411,6 +411,125 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Gmail-forbindelse", + description: + "Giv din agent mulighed for at interagere med Gmail – søg efter e-mails, læs samtaler, oprette udkast, sende e-mails og administrere din indbakke. Læs dokumentationen.", + multiUserWarning: + "Integration med Gmail er ikke tilgængelig i multi-bruger-tilstand af sikkerhedsmæssige årsager. For at bruge denne funktion, bedes du deaktivere multi-bruger-tilstanden.", + configuration: "Konfiguration af Gmail", + deploymentId: "Implementerings-ID", + deploymentIdHelp: "Deployment-ID'en fra din Google Apps Script webapp", + apiKey: "API-nøgle", + apiKeyHelp: + "API-nøglen, du har konfigureret i din Google Apps Script-implementering.", + configurationRequired: + "Venligst konfigurer Deployment ID og API-nøglen for at aktivere Gmail-funktionaliteten.", + configured: "Konfigureret", + searchSkills: "Søgeteknikker...", + noSkillsFound: "Ingen resultater fundet, der matcher din søgning.", + categories: { + search: { + title: "Søg og læs e-mails", + description: "Søg og læs e-mails fra din Gmail-indbakke", + }, + drafts: { + title: "Udkast til e-mails", + description: "Opret, rediger og administrer udkast til e-mails", + }, + send: { + title: "Send og svar på e-mails", + description: "Send e-mails og svar på tråde øjeblikkeligt", + }, + threads: { + title: "Administrer e-mailtråde", + description: + "Administrer e-mailtråde – marker som læst/ulæst, arkiver, slet", + }, + account: { + title: "Statistik for integration", + description: + "Se statistik for din e-postindbakke og kontoinformation.", + }, + }, + skills: { + search: { + title: "Søg i e-mails", + description: + "Søg efter e-mails ved hjælp af Gmail's forespørgselssprog", + }, + readThread: { + title: "Læs tråd", + description: "Læs hele e-mailtråden sorteret efter ID", + }, + createDraft: { + title: "Opret udkast", + description: "Opret et nyt udkast til en e-mail", + }, + createDraftReply: { + title: "Opret udkast", + description: "Opret et udkast til et svar på en eksisterende tråd.", + }, + updateDraft: { + title: "Opdateret udkast", + description: "Opdater en eksisterende udkast til en e-mail", + }, + getDraft: { + title: "Få udkast", + description: "Hent en bestemt udkast baseret på ID", + }, + listDrafts: { + title: "Udkast til lister", + description: "Vis alle udkastede e-mails", + }, + deleteDraft: { + title: "Slet udkast", + description: "Slet et udkast til e-mail", + }, + sendDraft: { + title: "Send udkast", + description: "Send en eksisterende udkast til en e-mail", + }, + sendEmail: { + title: "Send e-mail", + description: "Send en e-mail med det samme", + }, + replyToThread: { + title: "Svar på tråd", + description: "Svar på en e-mailtråd med det samme", + }, + markRead: { + title: "Mark Read", + description: "Marker en tråd som læst", + }, + markUnread: { + title: "Marker som ikke læst", + description: "Marker en tråd som ikke læst", + }, + moveToTrash: { + title: "Flyt til papirkurven", + description: "Flyt en tråd til papirkurven", + }, + moveToArchive: { + title: "Arkiv", + description: "Arkiver tråden", + }, + moveToInbox: { + title: "Flyt til indbakken", + description: "Flyt en tråd til indbakken", + }, + getMailboxStats: { + title: "Statistik for postkasse", + description: + "Få oplysninger om antallet af ulæste beskeder og statistik for din e-mailindbakke.", + }, + getInbox: { + title: "Åbn indbakken", + description: + "En effektiv måde at hente e-mails fra din Gmail-indbakke", + }, + }, + }, }, mcp: { title: "MCP-servere", diff --git a/frontend/src/locales/de/common.js b/frontend/src/locales/de/common.js index 46c7b599..396829b3 100644 --- a/frontend/src/locales/de/common.js +++ b/frontend/src/locales/de/common.js @@ -417,6 +417,131 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Gmail-Verbindung", + description: + "Ermöglichen Sie Ihrem Agenten, mit Gmail zu interagieren: E-Mails durchsuchen, E-Mail-Threads lesen, Entwürfe erstellen, E-Mails senden und Ihren Posteingang verwalten. Lesen Sie die Dokumentation.", + multiUserWarning: + "Die Integration mit Gmail ist aus Sicherheitsgründen nicht im Mehrbenutzermodus verfügbar. Bitte deaktivieren Sie den Mehrbenutzermodus, um diese Funktion zu nutzen.", + configuration: "Gmail-Konfiguration", + deploymentId: "Deployment-ID", + deploymentIdHelp: + "Die Bereitstellungs-ID Ihrer Google Apps Script Webanwendung", + apiKey: "API-Schlüssel", + apiKeyHelp: + "Der API-Schlüssel, den Sie in Ihrer Google Apps Script-Bereitstellung konfiguriert haben", + configurationRequired: + "Bitte konfigurieren Sie die Deployment-ID und den API-Schlüssel, um die Gmail-Funktionen zu aktivieren.", + configured: "Konfiguriert", + searchSkills: "Suchfähigkeiten...", + noSkillsFound: "Keine Ergebnisse zu Ihrer Suche.", + categories: { + search: { + title: "Nachrichten suchen und lesen", + description: + "Suchen und lesen Sie E-Mails aus Ihrem Gmail-Posteingang.", + }, + drafts: { + title: "Entwurf-E-Mails", + description: + "Erstellen, bearbeiten und verwalten von E-Mail-Entwürfen", + }, + send: { + title: "E-Mails senden und beantworten", + description: + "Senden Sie E-Mails und antworten Sie sofort auf Nachrichten.", + }, + threads: { + title: "E-Mail-Verläufe verwalten", + description: + "E-Mail-Threads verwalten – als gelesen/unleserlich markieren, archivieren, in den Papierkorb verschieben", + }, + account: { + title: "Statistiken zur Integration", + description: + "Anzeigen von Postfachstatistiken und Kontoinformationen", + }, + }, + skills: { + search: { + title: "E-Mails durchsuchen", + description: "E-Mails mit der Gmail-Suchsyntax durchsuchen", + }, + readThread: { + title: "Den Thread lesen", + description: + "Lesen Sie den vollständigen E-Mail-Thread anhand der ID", + }, + createDraft: { + title: "Entwurf erstellen", + description: "Erstelle eine neue Entwurf-E-Mail", + }, + createDraftReply: { + title: "Entwurf für Antwort erstellen", + description: + "Erstellen Sie eine Entwurfsantwort an ein bestehendes Thema.", + }, + updateDraft: { + title: "Entwurf aktualisieren", + description: "Eine bestehende Entwurf-E-Mail aktualisieren", + }, + getDraft: { + title: "Entwurf anfordern", + description: + "Eine bestimmte Entwurfversion anhand ihrer ID abrufen.", + }, + listDrafts: { + title: "Entwürfe", + description: "Liste alle Entwurf-E-Mails auf", + }, + deleteDraft: { + title: "Entwurf löschen", + description: "Einen Entwurf für eine E-Mail löschen", + }, + sendDraft: { + title: "Entwurf senden", + description: "Senden Sie eine bestehende Entwurf-E-Mail", + }, + sendEmail: { + title: "E-Mail senden", + description: "Senden Sie sofort eine E-Mail.", + }, + replyToThread: { + title: "Antwort auf den Thread", + description: "Antworten Sie umgehend auf einen E-Mail-Thread.", + }, + markRead: { + title: "Mark Read", + description: "Markiere einen Thread als gelesen.", + }, + markUnread: { + title: "Als nicht gelesen markieren", + description: "Markiere einen Thread als nicht gelesen.", + }, + moveToTrash: { + title: "In den Papierkorb verschieben", + description: "Ein Thema in den Papierkorb verschieben", + }, + moveToArchive: { + title: "Archiv", + description: "Thread archivieren", + }, + moveToInbox: { + title: "Zum Posteingang verschieben", + description: "Einen Thread in den Posteingang verschieben", + }, + getMailboxStats: { + title: "Statistiken für E-Mail-Postfach", + description: + "Erhalten Sie Informationen über die Anzahl nicht gelesener E-Mails und Statistiken für Ihr Postfach.", + }, + getInbox: { + title: "E-Mail-Postfach öffnen", + description: + "Ein einfacher und effizienter Weg, um E-Mails aus dem Gmail-Posteingang zu erhalten.", + }, + }, + }, }, "performance-warning": "Die Leistung von LLMs, die keine explizite Unterstützung für das Aufrufen von Tools bieten, hängt stark von den Fähigkeiten und der Genauigkeit des Modells ab. Einige Fähigkeiten können eingeschränkt oder nicht funktionsfähig sein.", diff --git a/frontend/src/locales/en/common.js b/frontend/src/locales/en/common.js index 26c18276..7154c280 100644 --- a/frontend/src/locales/en/common.js +++ b/frontend/src/locales/en/common.js @@ -420,6 +420,122 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail Connector", + description: + "Enable your agent to interact with Gmail - search emails, read threads, compose drafts, send emails, and manage your inbox. Read the documentation.", + multiUserWarning: + "Gmail integration is not available in multi-user mode for security reasons. Please disable multi-user mode to use this feature.", + configuration: "Gmail Configuration", + deploymentId: "Deployment ID", + deploymentIdHelp: + "The deployment ID from your Google Apps Script web app", + apiKey: "API Key", + apiKeyHelp: + "The API key you configured in your Google Apps Script deployment", + configurationRequired: + "Please configure the Deployment ID and API Key to enable Gmail skills.", + configured: "Configured", + searchSkills: "Search skills...", + noSkillsFound: "No skills match your search.", + categories: { + search: { + title: "Search & Read Emails", + description: "Search and read emails from your Gmail inbox", + }, + drafts: { + title: "Draft Emails", + description: "Create, edit, and manage email drafts", + }, + send: { + title: "Send & Reply to Emails", + description: "Send emails and reply to threads immediately", + }, + threads: { + title: "Manage Email Threads", + description: + "Manage email threads - mark read/unread, archive, trash", + }, + account: { + title: "Integration Statistics", + description: "View mailbox statistics and account information", + }, + }, + skills: { + getInbox: { + title: "Get Inbox", + description: "Streamlined way to get the inbox emails from Gmail", + }, + search: { + title: "Search Emails", + description: "Search emails using Gmail query syntax", + }, + readThread: { + title: "Read Thread", + description: "Read a full email thread by ID", + }, + createDraft: { + title: "Create Draft", + description: "Create a new draft email", + }, + createDraftReply: { + title: "Create Draft Reply", + description: "Create a draft reply to an existing thread", + }, + updateDraft: { + title: "Update Draft", + description: "Update an existing draft email", + }, + getDraft: { + title: "Get Draft", + description: "Retrieve a specific draft by ID", + }, + listDrafts: { + title: "List Drafts", + description: "List all draft emails", + }, + deleteDraft: { + title: "Delete Draft", + description: "Delete a draft email", + }, + sendDraft: { + title: "Send Draft", + description: "Send an existing draft email", + }, + sendEmail: { + title: "Send Email", + description: "Send an email immediately", + }, + replyToThread: { + title: "Reply to Thread", + description: "Reply to an email thread immediately", + }, + markRead: { + title: "Mark Read", + description: "Mark a thread as read", + }, + markUnread: { + title: "Mark Unread", + description: "Mark a thread as unread", + }, + moveToTrash: { + title: "Move to Trash", + description: "Move a thread to trash", + }, + moveToArchive: { + title: "Archive", + description: "Archive a thread", + }, + moveToInbox: { + title: "Move to Inbox", + description: "Move a thread to inbox", + }, + getMailboxStats: { + title: "Mailbox Stats", + description: "Get unread counts and mailbox statistics", + }, + }, + }, default_skill: "By default, this skill is enabled, but you can disable it if you don't want it to be available to the agent.", }, diff --git a/frontend/src/locales/es/common.js b/frontend/src/locales/es/common.js index 8d01948d..207b987a 100644 --- a/frontend/src/locales/es/common.js +++ b/frontend/src/locales/es/common.js @@ -423,6 +423,133 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Conector de GMail", + description: + "Permita que su agente interactúe con Gmail: buscar correos electrónicos, leer hilos, redactar borradores, enviar correos electrónicos y gestionar su bandeja de entrada. Consulte la documentación.", + multiUserWarning: + "La integración con Gmail no está disponible en el modo para múltiples usuarios por razones de seguridad. Para utilizar esta función, por favor, desactive el modo para múltiples usuarios.", + configuration: "Configuración de Gmail", + deploymentId: "ID de despliegue", + deploymentIdHelp: + "El ID de implementación de tu aplicación web de Google Apps Script", + apiKey: "Clave API", + apiKeyHelp: + "La clave de API que configuraste en la implementación de tu Google Apps Script.", + configurationRequired: + "Por favor, configure el ID de implementación y la clave de API para habilitar las funciones de Gmail.", + configured: "Configurado", + searchSkills: "Habilidades de búsqueda...", + noSkillsFound: "No se encontraron coincidencias con tu búsqueda.", + categories: { + search: { + title: "Buscar y leer correos electrónicos", + description: + "Busque y lea correos electrónicos de su bandeja de entrada de Gmail.", + }, + drafts: { + title: "Plantillas de correos electrónicos", + description: + "Crea, edita y gestiona borradores de correo electrónico.", + }, + send: { + title: "Enviar y responder a correos electrónicos", + description: + "Envía correos electrónicos y responde a las conversaciones de inmediato.", + }, + threads: { + title: "Gestionar hilos de correo electrónico", + description: + "Gestionar hilos de correo electrónico: marcar como leído/no leído, archivar, eliminar", + }, + account: { + title: "Estadísticas de integración", + description: + "Visualice estadísticas de la bandeja de entrada y información de la cuenta.", + }, + }, + skills: { + search: { + title: "Buscar correos electrónicos", + description: + "Busque correos electrónicos utilizando la sintaxis de consulta de Gmail.", + }, + readThread: { + title: "Leer el hilo", + description: + "Lee la conversación completa de correo electrónico, ordenada por ID.", + }, + createDraft: { + title: "Crear borrador", + description: "Crea un nuevo borrador de correo electrónico.", + }, + createDraftReply: { + title: "Crear respuesta preliminar", + description: + "Cree una respuesta preliminar a un hilo de discusión existente.", + }, + updateDraft: { + title: "Versión actualizada del borrador", + description: + "Actualizar un borrador de correo electrónico existente", + }, + getDraft: { + title: "Obtener borrador", + description: "Recuperar un borrador específico mediante su ID.", + }, + listDrafts: { + title: "Borradores", + description: "Enumera todos los correos electrónicos en proyecto.", + }, + deleteDraft: { + title: "Eliminar borrador", + description: "Eliminar un borrador de correo electrónico", + }, + sendDraft: { + title: "Enviar borrador", + description: "Enviar una versión previa de un correo electrónico.", + }, + sendEmail: { + title: "Enviar correo electrónico", + description: "Envía un correo electrónico inmediatamente.", + }, + replyToThread: { + title: "Responder a la discusión", + description: + "Responder a una conversación por correo electrónico de inmediato.", + }, + markRead: { + title: "Mark Read", + description: "Indicar que un hilo ha sido leído.", + }, + markUnread: { + title: "Marcar como no leído", + description: "Indicar que un hilo está sin leer.", + }, + moveToTrash: { + title: "Mover a la papelera", + description: "Mover un hilo a la papelera", + }, + moveToArchive: { + title: "Archivo", + description: "Archivar un hilo de conversación", + }, + moveToInbox: { + title: "Mover a la bandeja de entrada", + description: "Mover un hilo a la bandeja de entrada", + }, + getMailboxStats: { + title: "Estadísticas de la bandeja de entrada", + description: + "Obtén el número de correos no leídos y estadísticas de la bandeja de entrada.", + }, + getInbox: { + title: "Acceder a la bandeja de entrada", + description: + "Una forma sencilla de acceder a los correos electrónicos de la bandeja de entrada de Gmail.", + }, + }, + }, }, mcp: { title: "Servidores MCP", diff --git a/frontend/src/locales/et/common.js b/frontend/src/locales/et/common.js index 997fa203..1c3ee547 100644 --- a/frontend/src/locales/et/common.js +++ b/frontend/src/locales/et/common.js @@ -406,6 +406,123 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail-iga ühendamine", + description: + "Lase oma esindajal interakteeruda Gmailiga – otsida e-kirju, lugeda teemasid, luua esialgseid versioone, saatada e-kirju ja hallata oma postkasti. Vaata dokumentatsiooni.", + multiUserWarning: + "Gmaili integreerimine ei ole saadaval mitme kasutaja režiimis turvalisuse huvides. Palun deaktiveerige mitme kasutaja režiim, et kasutada seda funktsiooni.", + configuration: "Gmaili seadmine", + deploymentId: "Paigaldamis ID", + deploymentIdHelp: "Veebirakenduse Google Apps Scripti ID", + apiKey: "API võti", + apiKeyHelp: + "API võti, mida olete konfigureerinud oma Google Apps Scripti rakenduses.", + configurationRequired: + 'Palun konfigureerige "Deployment ID" ja API võti, et Gmaili funktsionaalsus oleks aktiivne.', + configured: "Konfigureeritud", + searchSkills: "otsinguteadused...", + noSkillsFound: "Leidke ei leitud sobivaid oskusi teie otsingu põhjal.", + categories: { + search: { + title: "Leia ja luge e-kirju", + description: "Kutsutage ja looge Gmaili postkasti olevad e-kirjad", + }, + drafts: { + title: "Esimesed kirjad", + description: "Loo, muuda ja hallata e-kirjade projekte", + }, + send: { + title: "Saada ja vastata e-kirjadele", + description: "Saada e-kirju ja vastata teemades kohe", + }, + threads: { + title: "Hallata e-kirjade seeriaid", + description: + "Hallata e-posti vestlusi – märgistada kui lugemata/lugemata, salvestada, kustutada", + }, + account: { + title: "Integreerimise statistika", + description: "Vaata postkasti statistikat ja konto teavet.", + }, + }, + skills: { + search: { + title: "E-kirjade otsing", + description: "Kutse e-kirju Gmaili küsimismängu sintaksiga", + }, + readThread: { + title: "Loe teemat", + description: "Vaata kogu e-kirjade sarja ID järgi", + }, + createDraft: { + title: "Loo esialgne versioon", + description: "Loo uus e-posti eelnäide", + }, + createDraftReply: { + title: "Loo esialgne vastus", + description: "Loo esialgne vastus olemasolevale teemale.", + }, + updateDraft: { + title: "Väljaanne", + description: "Värskendada olemasolevat e-kirja projekti", + }, + getDraft: { + title: "Vaata esialgne versioon", + description: "Taasta konkreetne versioon ID-täringuga", + }, + listDrafts: { + title: "Esialgne versioon", + description: "Loeda kõik e-kirjade plaani", + }, + deleteDraft: { + title: "Hüvata projekti", + description: "Hüvata e-kirja, mis on loodud", + }, + sendDraft: { + title: "Saada projekti", + description: "Saada olemasolev e-kirja esialgne versioon", + }, + sendEmail: { + title: "Saatke e-kiri", + description: "Saatke e-kiri kohe", + }, + replyToThread: { + title: "Vastata teemale", + description: "Vasta e-posti teemale kohe", + }, + markRead: { + title: "Mark Read", + description: "Märki, et teema on lugemata.", + }, + markUnread: { + title: "Märgi kui vaatamata", + description: "Märki, et teema on lugemata", + }, + moveToTrash: { + title: "Saada jäätmeteks", + description: "Liigu teema prügikasti", + }, + moveToArchive: { + title: "Arhiiv", + description: "Salvatage teema", + }, + moveToInbox: { + title: "Liiguta postkasti", + description: 'Liiga "Saadud" pildile', + }, + getMailboxStats: { + title: "Postkasti statistika", + description: + "Vaata, mitu ebakirjutatud kirja on, ning postkasti statistikat.", + }, + getInbox: { + title: "Ava postkasti", + description: + "Lihtne viis, kuidas saada Gmailist oma postkasti e-kirjad.", + }, + }, + }, }, mcp: { title: "MCP-serverid", diff --git a/frontend/src/locales/fa/common.js b/frontend/src/locales/fa/common.js index 8e71828b..06ef037f 100644 --- a/frontend/src/locales/fa/common.js +++ b/frontend/src/locales/fa/common.js @@ -408,6 +408,124 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "اتصال‌دهنده برای GMail", + description: + "به اپراتور خود اجازه دهید تا با Gmail تعامل داشته باشد: جستجو در ایمیل‌ها، خواندن موضوعات، نوشتن پیش‌نویس‌ها، ارسال ایمیل‌ها و مدیریت صندوق ورودی. برای اطلاعات بیشتر، مستندات را مطالعه کنید.", + multiUserWarning: + "ادغام با جی‌میل در حالت چند کاربر به دلیل مسائل امنیتی، امکان‌پذیر نیست. لطفاً حالت چند کاربر را غیرفعال کنید تا از این ویژگی استفاده کنید.", + configuration: "تنظیمات جی‌میل", + deploymentId: "شناسه استقرار", + deploymentIdHelp: "شناسه (ID) مربوط به اجرای اسکریپت وب در Google Apps", + apiKey: "کلید API", + apiKeyHelp: + "کلید API که در فرایند استقرار اسکریپت‌های Google Apps خود تنظیم کرده‌اید.", + configurationRequired: + "لطفاً شناسه استقرار و کلید API را تنظیم کنید تا قابلیت‌های Gmail فعال شوند.", + configured: "تنظیم شده", + searchSkills: "مهارت‌های جستجو...", + noSkillsFound: "هیچ یک از مهارت‌های موجود با جستجوی شما مطابقت ندارند.", + categories: { + search: { + title: "جستجو و خواندن ایمیل‌ها", + description: "جستجو و خواندن ایمیل‌ها از صندوق ورودی Gmail خود", + }, + drafts: { + title: "پیش‌نویس ایمیل‌ها", + description: "ایجاد، ویرایش و مدیریت پیش‌نویس‌های ایمیل", + }, + send: { + title: "ارسال و پاسخ به ایمیل‌ها", + description: "ایمیل‌ها را ارسال و به فوریت به موضوعات پاسخ دهید.", + }, + threads: { + title: "مدیریت موضوعات ایمیل", + description: + "مدیریت تر وهای ایمیل - علامت‌گذاری به عنوان خوانده/خوانده نشده، آرشیو، حذف", + }, + account: { + title: "آمار ادغام", + description: "دسترسی به آمار صندوق ایمیل و اطلاعات حساب", + }, + }, + skills: { + search: { + title: "جستجو در ایمیل‌ها", + description: "جستجو در ایمیل‌ها با استفاده از سینتکس کوئری Gmail", + }, + readThread: { + title: "خواندن موضوع", + description: "خواندن کل یک رشته ایمیل بر اساس شناسه (ID)", + }, + createDraft: { + title: "ایجاد پیش‌نویس", + description: "یک پیش‌نویس ایمیل جدید ایجاد کنید.", + }, + createDraftReply: { + title: "ایجاد پاسخ پیش‌نویس", + description: "یک پیش‌نویس برای پاسخ به یک موضوع موجود ایجاد کنید.", + }, + updateDraft: { + title: "نسخه اصلاح‌شده", + description: "به‌روزرسانی یک نسخه پیش‌نویس ایمیل موجود", + }, + getDraft: { + title: "دریافت نسخه پیش‌نویس", + description: "بازیابی یک نسخه خاص بر اساس شناسه", + }, + listDrafts: { + title: "فهرست پیش‌نویس‌ها", + description: "لیست تمام ایمیل‌های پیش‌نویس", + }, + deleteDraft: { + title: "حذف پیش‌نویس", + description: "حذف یک پیش‌نویس ایمیل", + }, + sendDraft: { + title: "ارسال پیش‌نویس", + description: "ارسال یک نسخه پیش‌نویس از ایمیل موجود", + }, + sendEmail: { + title: "ارسال ایمیل", + description: "بلافاصله یک ایمیل ارسال کنید.", + }, + replyToThread: { + title: "پاسخ به موضوع", + description: "پاسخ فوری به یک رشته ایمیل", + }, + markRead: { + title: "مارک ریڈ", + description: 'یک موضوع را به عنوان "خوانده شده" علامت‌گذاری کنید.', + }, + markUnread: { + title: 'به عنوان "غیر خوانده شده" علامت بزنید', + description: + 'یک موضوع را به عنوان "غیرخوانده شده" علامت‌گذاری کنید.', + }, + moveToTrash: { + title: "به سطل زباله بفرستید", + description: "یک موضوع را به سطل زباله منتقل کنید.", + }, + moveToArchive: { + title: "آرشیو", + description: "فایل آرشیو کردن یک موضوع", + }, + moveToInbox: { + title: "انتقال به پوشه ورودی", + description: "یک موضوع را به صندوق ورودی منتقل کنید.", + }, + getMailboxStats: { + title: "آمار صندوق ایمیل", + description: + "تعداد ایمیل‌های دریافتی و آمار صندوق ایمیل را دریافت کنید.", + }, + getInbox: { + title: "دسترسی به صندوق ورودی", + description: + "یک راه ساده برای دریافت ایمیل‌های صندوق ورودی از جی‌میل", + }, + }, + }, }, mcp: { title: "سرورهای MCP", diff --git a/frontend/src/locales/fr/common.js b/frontend/src/locales/fr/common.js index 012d6f8c..b98ca546 100644 --- a/frontend/src/locales/fr/common.js +++ b/frontend/src/locales/fr/common.js @@ -413,6 +413,132 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Connecteur GMail", + description: + "Permettez à votre agent d'interagir avec Gmail : rechercher des e-mails, lire les conversations, rédiger des brouillons, envoyer des e-mails et gérer votre boîte de réception. Consultez la documentation.", + multiUserWarning: + "L'intégration avec Gmail n'est pas disponible en mode multi-utilisateurs pour des raisons de sécurité. Veuillez désactiver le mode multi-utilisateurs pour utiliser cette fonctionnalité.", + configuration: "Configuration de Gmail", + deploymentId: "Identifiant de déploiement", + deploymentIdHelp: + "L'identifiant de déploiement de votre application web Google Apps Script", + apiKey: "Clé API", + apiKeyHelp: + "La clé API que vous avez configurée lors de votre déploiement de Google Apps Script", + configurationRequired: + "Veuillez configurer l'ID de déploiement et la clé API pour activer les fonctionnalités de Gmail.", + configured: "Configuré", + searchSkills: "Compétences de recherche...", + noSkillsFound: "Aucune compétence ne correspond à votre recherche.", + categories: { + search: { + title: "Rechercher et lire des e-mails", + description: + "Recherchez et lisez vos e-mails dans votre boîte de réception Gmail.", + }, + drafts: { + title: "Modèles de courriels", + description: "Créer, modifier et gérer des brouillons d'e-mails.", + }, + send: { + title: "Envoyer et répondre aux e-mails", + description: + "Envoyez des e-mails et répondez immédiatement aux discussions.", + }, + threads: { + title: "Gérer les conversations par e-mail", + description: + "Gérer les conversations par e-mail : marquer comme lu/non lu, archiver, supprimer", + }, + account: { + title: "Statistiques d'intégration", + description: + "Consultez les statistiques de votre boîte de réception et les informations de votre compte.", + }, + }, + skills: { + search: { + title: "Rechercher dans les e-mails", + description: + "Rechercher des e-mails en utilisant la syntaxe de recherche de Gmail", + }, + readThread: { + title: "Lire le fil de discussion", + description: "Lire l'intégralité d'une conversation par ID", + }, + createDraft: { + title: "Créer une version préliminaire", + description: "Créez une nouvelle version de l'e-mail.", + }, + createDraftReply: { + title: "Créer une réponse brouillon", + description: + "Rédiger une réponse préliminaire à un fil de discussion existant.", + }, + updateDraft: { + title: "Mise à jour de la version préliminaire", + description: "Mettre à jour un brouillon de courriel existant", + }, + getDraft: { + title: "Obtenir la version préliminaire", + description: + "Récupérer une version spécifique par son identifiant.", + }, + listDrafts: { + title: "Propositions/Brouillons", + description: "Énumérer tous les courriels en brouillon.", + }, + deleteDraft: { + title: "Supprimer la version brouillon", + description: "Supprimer une brouillon de courriel", + }, + sendDraft: { + title: "Envoyer une version préliminaire", + description: "Envoyer une version existante d'un courriel", + }, + sendEmail: { + title: "Envoyer un e-mail", + description: "Envoyez un courriel immédiatement.", + }, + replyToThread: { + title: "Répondre à la discussion", + description: + "Répondre immédiatement à une conversation par e-mail.", + }, + markRead: { + title: "Mark Read", + description: "Indiquer qu'un fil de discussion a été lu", + }, + markUnread: { + title: "Signaler comme non lu", + description: "Indiquer qu'un fil de discussion est non lu", + }, + moveToTrash: { + title: "Envoyer à la poubelle", + description: "Déplacer un fil dans la corbeille d'ordures", + }, + moveToArchive: { + title: "Archives", + description: "Archiver une discussion", + }, + moveToInbox: { + title: "Envoyer vers la boîte de réception", + description: + "Déplacer un fil de discussion dans la boîte de réception", + }, + getMailboxStats: { + title: "Statistiques de la boîte de réception", + description: + "Obtenez le nombre d'emails non lus et les statistiques de votre boîte de réception.", + }, + getInbox: { + title: "Accéder à la boîte de réception", + description: + "Une méthode simple et efficace pour récupérer les e-mails de votre boîte de réception Gmail.", + }, + }, + }, }, mcp: { title: "Serveurs MCP", diff --git a/frontend/src/locales/he/common.js b/frontend/src/locales/he/common.js index 34bc159d..bab37afc 100644 --- a/frontend/src/locales/he/common.js +++ b/frontend/src/locales/he/common.js @@ -405,6 +405,123 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "חיבור ל-GMail", + description: + 'אפשר לאгент שלכם לתקשר עם Gmail – לחפש הודעות דוא"ל, לקרוא דיונים, ליצור טיוטות, לשלוח הודעות דוא"ל ולנהל תיבת הדואר. קרא את התיעוד.', + multiUserWarning: + "שילוב עם ג'ימל אינו זמין במצב רב משתמשים מטעמי אבטחה. אנא כבו את מצב רב משתמשים כדי להשתמש בתכונה זו.", + configuration: "הגדרת חשבון Gmail", + deploymentId: "מזהה הפריסה", + deploymentIdHelp: "מזהה הפריסה מהאפליקציה שלך של Google Apps Script.", + apiKey: "מפתח API", + apiKeyHelp: + "מפתח ה-API שבה הגדרת בסביבת העבודה של Google Apps Script שלך.", + configurationRequired: + "אנא הגדירו את מזהה ההפצה ואת מפתח ה-API כדי לאפשר את יכולות Gmail.", + configured: "הוגדר", + searchSkills: "כישורים לחיפוש...", + noSkillsFound: "לא נמצאו תוצאות התואמות את החיפוש שלך.", + categories: { + search: { + title: 'חיפוש וקריאת הודעות דוא"ל', + description: 'חפש וקרא הודעות דוא"ל מהתיבת הדוא"ל של ג\'ימל שלך.', + }, + drafts: { + title: 'הודעות דוא"ל לדוגמה', + description: 'יצירה, עריכה וניהול של טיוטות של הודעות דוא"ל.', + }, + send: { + title: "שליחה ומתן מענה למיילים", + description: "שלחו מיילים ונענו לדיונים באופן מיידי.", + }, + threads: { + title: 'ניהול שרשראות דוא"ל', + description: + 'ניהול תכתובות דוא"ל – סימון כ"קרא/לא קרא", ארכיון, תיבת זבל', + }, + account: { + title: "נתונים על שילוב", + description: "צפו בסטטיסטיקות של תיבת הדואר ובתמונות החשבון", + }, + }, + skills: { + search: { + title: 'חיפוש הודעות דוא"ל', + description: "חיפוש הודעות דוא\"ל באמצעות תחביר שאילתות של ג'ימייל", + }, + readThread: { + title: "קרא את השרשור", + description: "קרא את כל שרשרת האימיילים שנוצרה על ידי ID.", + }, + createDraft: { + title: "יצירת טיוטה", + description: "צור טיוטת אימייל חדשה", + }, + createDraftReply: { + title: "יצירת תגובה ראשונית", + description: "צור תגובה ראשונית לפורום קיים.", + }, + updateDraft: { + title: "גרסה עדכנית", + description: 'עדכן הודעת דוא"ל קיימת', + }, + getDraft: { + title: "קבל טיוטה", + description: "השגת גרסה ספציפית לפי מספר זיהוי", + }, + listDrafts: { + title: "גרסאות טיוטה", + description: "רשום את כל ההודעות המאובזרות", + }, + deleteDraft: { + title: "מחיקת טיוטה", + description: "מחיקת תגובה מקושטת", + }, + sendDraft: { + title: "שלח טיוטה", + description: "שלח גרסה קיימת של מייל", + }, + sendEmail: { + title: "שלח אימייל", + description: "שלח מייל באופן מיידי", + }, + replyToThread: { + title: "תגובה לדיון", + description: "התגובה מיידית לשרשור אימיילים", + }, + markRead: { + title: "מרק רד", + description: 'סמנו נושא כ"קרא"', + }, + markUnread: { + title: "סמן כלא קרא", + description: "סמנו נושא כלא קרא", + }, + moveToTrash: { + title: "העבר למיחזור", + description: "העברת נושא לקבצי האשפה", + }, + moveToArchive: { + title: "ארכיון", + description: "שמור את הדיון", + }, + moveToInbox: { + title: "העבר לסמאר", + description: "העבר את הדיון לתיבת הדואר הנכנס", + }, + getMailboxStats: { + title: "סטטיסטיקות של תיבת דואר", + description: + "קבל ספירה של הודעות שטרם נקראו וסטטיסטיקות של תיבת הדואר.", + }, + getInbox: { + title: "קבל תיבת הדואר", + description: + 'דרך פשוטה ומהירה לקבל את הודעות הדוא"ל מהתיבת הדוא"ל של ג\'ימל', + }, + }, + }, }, mcp: { title: "שרתי MCP", diff --git a/frontend/src/locales/it/common.js b/frontend/src/locales/it/common.js index c50c7fe4..18f4bd17 100644 --- a/frontend/src/locales/it/common.js +++ b/frontend/src/locales/it/common.js @@ -414,6 +414,132 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Connettore per GMail", + description: + "Permetti al tuo agente di interagire con Gmail: cercare email, leggere conversazioni, creare bozze, inviare email e gestire la tua casella di posta. Consulta la documentazione.", + multiUserWarning: + "L'integrazione con Gmail non è disponibile in modalità multi-utente per motivi di sicurezza. Per utilizzare questa funzione, è necessario disattivare la modalità multi-utente.", + configuration: "Configurazione di Gmail", + deploymentId: "ID di distribuzione", + deploymentIdHelp: + "L'ID di implementazione della tua applicazione web Google Apps Script", + apiKey: "Chiave API", + apiKeyHelp: + "La chiave API che hai configurato nella tua implementazione di Google Apps Script", + configurationRequired: + "Si prega di configurare l'ID di distribuzione e la chiave API per abilitare le funzionalità di Gmail.", + configured: "Configurato", + searchSkills: "Competenze di ricerca...", + noSkillsFound: + "Non sono state trovate corrispondenze per la tua ricerca.", + categories: { + search: { + title: "Cerca e leggi le email", + description: + "Cerca e leggi le email dalla tua casella di posta Gmail.", + }, + drafts: { + title: "Bozze di email", + description: "Creare, modificare e gestire bozze di email.", + }, + send: { + title: "Invia e rispondi alle e-mail", + description: + "Invia e-mail e rispondi immediatamente ai thread di discussione.", + }, + threads: { + title: "Gestire le conversazioni via email", + description: + "Gestire le conversazioni via email: contrassegnare come lette/non lette, archiviare, eliminare", + }, + account: { + title: "Statistiche sull'integrazione", + description: + "Visualizza le statistiche della casella di posta e le informazioni sull'account.", + }, + }, + skills: { + search: { + title: "Cerca email", + description: + "Cerca email utilizzando la sintassi di ricerca di Gmail", + }, + readThread: { + title: "Leggi la discussione", + description: + "Leggi l'intera conversazione via email, ordinata per ID.", + }, + createDraft: { + title: "Crea una bozza", + description: "Crea una nuova bozza di e-mail", + }, + createDraftReply: { + title: "Crea una bozza di risposta", + description: "Crea una bozza di risposta a un thread esistente.", + }, + updateDraft: { + title: "Versione aggiornata", + description: "Aggiornare una bozza di email esistente", + }, + getDraft: { + title: "Visualizza la bozza", + description: "Recupera una bozza specifica utilizzando l'ID.", + }, + listDrafts: { + title: "Proposte", + description: "Elenca tutte le bozze di email.", + }, + deleteDraft: { + title: "Elimina bozza", + description: "Elimina una bozza di e-mail", + }, + sendDraft: { + title: "Invia versione bozze", + description: "Invia una bozza di email esistente.", + }, + sendEmail: { + title: "Invia e-mail", + description: "Invia un'e-mail immediatamente.", + }, + replyToThread: { + title: "Rispondere al thread", + description: + "Rispondere immediatamente a una conversazione via email.", + }, + markRead: { + title: "Mark Read", + description: "Segna un thread come letto", + }, + markUnread: { + title: "Segna come non letto", + description: "Segna un thread come non letto.", + }, + moveToTrash: { + title: "Elimina", + description: "Sposta un thread nel cestino.", + }, + moveToArchive: { + title: "Archivio", + description: "Archivia la conversazione", + }, + moveToInbox: { + title: "Sposta nella cartella di posta in arrivo", + description: + "Sposta la conversazione nella casella di posta in arrivo.", + }, + getMailboxStats: { + title: "Statistiche della casella di posta elettronica", + description: + "Ottieni il numero di email non lette e le statistiche della casella di posta.", + }, + getInbox: { + title: "Accedi alla casella di posta", + description: + "Un modo semplice ed efficiente per accedere alle email nella casella di posta di Gmail.", + }, + }, + }, }, mcp: { title: "Server MCP", diff --git a/frontend/src/locales/ja/common.js b/frontend/src/locales/ja/common.js index 45efa55b..0c873ee3 100644 --- a/frontend/src/locales/ja/common.js +++ b/frontend/src/locales/ja/common.js @@ -404,6 +404,121 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Gmail 接続", + description: + "エージェントがGmailと連携できるようにする:メールの検索、スレッドの閲覧、ドラフトの作成、メールの送信、およびインボックスの管理を可能にします。詳細については、ドキュメントを参照。", + multiUserWarning: + "セキュリティ上の理由から、Gmailとの連携はマルチユーザーモードでは利用できません。この機能を使用するには、まずマルチユーザーモードを無効にしてください。", + configuration: "Gmail の設定", + deploymentId: "デプロイメントID", + deploymentIdHelp: + "あなたのGoogle Apps ScriptウェブアプリケーションのデプロイメントID", + apiKey: "APIキー", + apiKeyHelp: "Google Apps Script のデプロイ時に設定した API キー", + configurationRequired: + "Gmail の機能を有効にするには、デプロイメント ID と API キーを設定してください。", + configured: "設定済み", + searchSkills: "検索スキル...", + noSkillsFound: "検索条件に合致するスキルは見つかりませんでした。", + categories: { + search: { + title: "メールの検索と閲覧", + description: "Gmail の受信トレイから、メールを検索および閲覧する", + }, + drafts: { + title: "サンプルメール", + description: "メールの作成、編集、および管理", + }, + send: { + title: "メールの送信と返信", + description: "メールを送信し、スレッドへの返信をすぐに行う。", + }, + threads: { + title: "メールのトピックを管理する", + description: + "メールのトピックを管理する - 既読/未読のマーク、アーカイブ、削除", + }, + account: { + title: "統合に関する統計", + description: "メールボックスの統計情報とアカウント情報を表示する", + }, + }, + skills: { + search: { + title: "メールを検索する", + description: "Gmail のクエリ構文を使用して、メールを検索する", + }, + readThread: { + title: "スレッドを読む", + description: "IDでメールの全文を閲覧する", + }, + createDraft: { + title: "ドラフト作成", + description: "新しいメールの草案を作成する", + }, + createDraftReply: { + title: "草案の返信を作成する", + description: "既存のスレッドに対する返信の草案を作成する", + }, + updateDraft: { + title: "ドラフトの更新", + description: "既存のメールドラフトを更新する", + }, + getDraft: { + title: "草案を入手", + description: "IDで特定のドラフトを取得する", + }, + listDrafts: { + title: "ドラフト案リスト", + description: "すべての草案メールの一覧を表示する", + }, + deleteDraft: { + title: "草案を削除", + description: "草案のメールを削除する", + }, + sendDraft: { + title: "草案を送信", + description: "既存のメールドラフトを送信する", + }, + sendEmail: { + title: "メールを送信する", + description: "すぐにメールを送信してください", + }, + replyToThread: { + title: "スレッドへの返信", + description: "メールのやり取りにすぐに返信する", + }, + markRead: { + title: "マーク・リード", + description: "スレッドを「読了」としてマークする", + }, + markUnread: { + title: "未読としてマーク", + description: "スレッドを「未読」としてマークする", + }, + moveToTrash: { + title: "ゴミ箱へ移動", + description: "スレッドをゴミ箱に移動する", + }, + moveToArchive: { + title: "アーカイブ", + description: "スレッドをアーカイブする", + }, + moveToInbox: { + title: "受信トレイへ移動", + description: "スレッドをインボックスに移動する", + }, + getMailboxStats: { + title: "メールボックスの統計情報", + description: "未読件数とメールボックスの統計情報を取得する", + }, + getInbox: { + title: "インボックスを開く", + description: "Gmail から受信したメールを効率的に取得する方法", + }, + }, + }, }, mcp: { title: "MCP サーバー", diff --git a/frontend/src/locales/ko/common.js b/frontend/src/locales/ko/common.js index c3a6240c..47833f31 100644 --- a/frontend/src/locales/ko/common.js +++ b/frontend/src/locales/ko/common.js @@ -409,6 +409,120 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail 연결기", + description: + "제 에이전트가 Gmail과 상호 작용할 수 있도록 설정하세요 - 이메일 검색, 스레드 읽기, 초안 작성, 이메일 전송, 그리고 받은 메일 관리 기능을 사용하세요. 문서를 참조하세요.", + multiUserWarning: + "Gmail 통합 기능은 보안상의 이유로 다중 사용자 모드에서는 사용할 수 없습니다. 이 기능을 사용하려면 다중 사용자 모드를 비활성화해 주세요.", + configuration: "Gmail 설정", + deploymentId: "배포 ID", + deploymentIdHelp: "당신의 Google Apps Script 웹 앱의 배포 ID", + apiKey: "API 키", + apiKeyHelp: "당신이 Google Apps Script 배포 시 설정한 API 키", + configurationRequired: + "Gmail 기능을 사용하려면 배포 ID와 API 키를 설정해 주세요.", + configured: "설정됨", + searchSkills: "검색 기술...", + noSkillsFound: "검색 결과와 일치하는 기술이 없습니다.", + categories: { + search: { + title: "이메일 검색 및 읽기", + description: "Gmail 계정에서 이메일을 검색하고 읽으세요.", + }, + drafts: { + title: "샘플 이메일", + description: "이메일 초안을 작성, 편집, 관리", + }, + send: { + title: "이메일 보내기 및 답변", + description: "이메일을 보내고, 토론 스레드에 즉시 응답", + }, + threads: { + title: "이메일 스레드 관리", + description: + "이메일 스레드 관리 - 읽음/미읽음 표시, 아카이브, 삭제", + }, + account: { + title: "통합 통계", + description: "메일함 통계 및 계정 정보 확인", + }, + }, + skills: { + search: { + title: "이메일 검색", + description: "Gmail 쿼리 구문을 사용하여 이메일 검색", + }, + readThread: { + title: "게시글 전체 읽기", + description: "ID를 기준으로 전체 이메일 스레드를 읽기", + }, + createDraft: { + title: "초안 작성", + description: "새로운 이메일 초안 작성", + }, + createDraftReply: { + title: "초안 답변 작성", + description: "기존 스레드에 대한 답변 초안 작성", + }, + updateDraft: { + title: "초안 업데이트", + description: "기존 이메일 초안을 업데이트합니다.", + }, + getDraft: { + title: "초안 보기", + description: "ID를 사용하여 특정 초안을 검색/불러오기", + }, + listDrafts: { + title: "초안 목록", + description: "모든 초안 이메일 목록을 표시", + }, + deleteDraft: { + title: "초안 삭제", + description: "초안 이메일을 삭제", + }, + sendDraft: { + title: "초안 보내기", + description: "기존 이메일 초안을 보내기", + }, + sendEmail: { + title: "이메일 보내기", + description: "즉시 이메일을 보내세요.", + }, + replyToThread: { + title: "게시글에 답변", + description: "이메일 스레드에 즉시 답변", + }, + markRead: { + title: "마크 리드", + description: '특정 게시글을 "읽음"으로 표시', + }, + markUnread: { + title: "미리 읽기", + description: '특정 스레드를 "읽지 않은 상태"로 표시', + }, + moveToTrash: { + title: "삭제", + description: "스레드를 쓰레기함으로 이동", + }, + moveToArchive: { + title: "보관", + description: "게시글을 보관", + }, + moveToInbox: { + title: "받은 편지함으로 이동", + description: "스레드를 받은 편지함으로 이동", + }, + getMailboxStats: { + title: "메일함 통계", + description: "읽지 않은 이메일 수 및 메일함 통계 확인", + }, + getInbox: { + title: "메일함 보기", + description: "Gmail에서 받은 이메일을 효율적으로 관리하는 방법", + }, + }, + }, }, mcp: { title: "MCP 서버", diff --git a/frontend/src/locales/lt/common.js b/frontend/src/locales/lt/common.js index cc798cd6..1e7aaecc 100644 --- a/frontend/src/locales/lt/common.js +++ b/frontend/src/locales/lt/common.js @@ -422,6 +422,126 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail sąsaja", + description: + "Įgalinkite savo agentą, kad galėtų interaktuoti su Gmail – ieškoti pašto žinučių, skaityti pokalbius, kurti projekte, siųsti pašto žinučių ir valdyti savo pašto dėžę. Peržiūrėkite dokumentaciją.", + multiUserWarning: + "„Gmail“ integracija negali būti naudojama kelių vartotojų režimu dėl saugumo priežasčių. Norėdami naudoti šią funkciją, prašome išjungti kelių vartotojų režimą.", + configuration: "Gmail konfigūracija", + deploymentId: "Įrenginio ID", + deploymentIdHelp: "Jūsų „Google Apps Script“ svetainės programos ID", + apiKey: "API raktas", + apiKeyHelp: + "„API“ raktas, kurį konfigūruojate savo „Google Apps Script“ programoje.", + configurationRequired: + "Prašome nustatyti „Deployment ID“ ir API raktą, kad būtų įgalintos Gmail funkcijos.", + configured: "Nustatytas", + searchSkills: "Paieškos įgūdžiai...", + noSkillsFound: "Nėra atitikčių jūsų paieškos kriterijams.", + categories: { + search: { + title: "Paieškos ir skaitymas el. paštą", + description: + "Paieškokite ir skaitykite el. laiimus iš savo „Gmail“ sąrašo", + }, + drafts: { + title: "Pagalbos el. pašto rašto projektai", + description: "Sukurkite, redaguo, ir valdykite el. pašto rašinius.", + }, + send: { + title: "Siųstis ir atsakyti el. pašto žinutėms", + description: + "Siųkite el. pašto žinutes ir atsakykite į diskusijų siužus nedelsiant.", + }, + threads: { + title: "Valdykite el. pašto žinias", + description: + "Valdykite el. pašto žinias – pažymėkite kaip „perskaityta“ arba „neperskaityta“, archyvuokite, ištrinkite.", + }, + account: { + title: "Integracijos statistika", + description: + "Peržiūrėkite pašto dėžės statistikos ir sąskaitos informaciją.", + }, + }, + skills: { + search: { + title: "Paieškos el. paštu", + description: + "Paieškokite el. laiimus naudodami Gmail paieškos sintaksę", + }, + readThread: { + title: "Peržiūrėti temą", + description: "Peržiūrėkite visą el. pašto seką pagal ID.", + }, + createDraft: { + title: "Sukurti projekto variantą", + description: "Sukurkite naują el. pašto projekto variantą", + }, + createDraftReply: { + title: "Sukurti projekto atsakymą", + description: "Sukurkite atsakinimo projektą esamai temai.", + }, + updateDraft: { + title: "Paskaitos projekto atnaujinimas", + description: "Atnaujinti esamą el. pašto projekto", + }, + getDraft: { + title: "Gaukite projekto variantą", + description: "Gauti konkretų variantą pagal ID", + }, + listDrafts: { + title: "Paskaitų planas", + description: "Įrašykite visus suvestus el. pašto projektus", + }, + deleteDraft: { + title: "Ištrinkti projekto", + description: "Ištrinkite projekto el. laišką", + }, + sendDraft: { + title: "Siųstis projekto", + description: "Siųstis esamą el. pašto projekto", + }, + sendEmail: { + title: "Siųstis el. pašto laišką", + description: "Nurodyti el. pašto žinutę nedelsiant", + }, + replyToThread: { + title: "Atsakymas į temą", + description: "Atsakyti į el. pašto žinią nedelsiant", + }, + markRead: { + title: "Markas Redas", + description: "Žymkite temą kaip perskaitytą", + }, + markUnread: { + title: "Žymėti kaip neperskaityta", + description: "Žymkite temą kaip neperskaitytą", + }, + moveToTrash: { + title: "Perkelti į šiukšlių sąvartą", + description: "Perkelkite temą į šiukšlių sąsandynę", + }, + moveToArchive: { + title: "Archivų", + description: "Įrašyti temą į archyvą", + }, + moveToInbox: { + title: "Perkelkite į „Įvesalą“", + description: "Perkelti temą į pašto dėžę", + }, + getMailboxStats: { + title: "Pašto dėžės statistika", + description: + "Gaukite neatsakytų žinučių skaičių ir pašto dėžės statistikos duomenis.", + }, + getInbox: { + title: "Peržiūrėti laiškų dėžę", + description: "Efektyvus būdas gauti el. laiškus iš „Gmail“ sąrašo", + }, + }, + }, }, mcp: { title: "MCP serveriai", diff --git a/frontend/src/locales/lv/common.js b/frontend/src/locales/lv/common.js index 9339bf2c..f5576716 100644 --- a/frontend/src/locales/lv/common.js +++ b/frontend/src/locales/lv/common.js @@ -416,6 +416,128 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail savienojums", + description: + "Ļauj savam pārstāvim interaktīvi strādāt ar Gmail – meklēt e-pastus, lasīt diskusijas, veidot projekte, nosūtīt e-pastus un pārvaldīt savu e-pasta kārtojumu. Izlasiet dokumentāciju.", + multiUserWarning: + "Gmail integrācija nav pieejama, kad izmantojat vairākus lietotājus, jo tas ir saistīts ar drošības apsvērumiem. Lūdzu, atslēgt vairāku lietotāju režimu, lai izmantotu šo funkciju.", + configuration: "Gmail konfigurācija", + deploymentId: "Ieraksta ID", + deploymentIdHelp: + "Jūsu Google Apps Script veidotās web lietojamās lietojamās ID", + apiKey: "API atslēga", + apiKeyHelp: + "API atslēga, ko jūs konfigurējāt savā Google Apps Script instalēšanā.", + configurationRequired: + 'Lūdzu, konfigurējiet "Deployment ID" un API atslēgu, lai aktivizētu Gmail funkcijas.', + configured: "Ierobežots", + searchSkills: "Meklēšanas prasmes...", + noSkillsFound: "Neatrodītas atbilstības jūsu meklējumiem.", + categories: { + search: { + title: "Meklē un lasiet e-pastus", + description: + "Meklējiet un lasiet e-pasta vēstnes no jūsu Gmail ievakā.", + }, + drafts: { + title: "Pamatraksti e-pasta vēstījumiem", + description: "Izveidot, rediģēt un pārvaldīt e-pasta rakstus", + }, + send: { + title: "Sūtiet un atbildiet uz e-pasta vēstījumiem", + description: + "Nosūtiet e-pasta ziņojus un atbildiet uz diskusiju tēriņiem nekavējoties.", + }, + threads: { + title: "Aizgrieziet e-pasta sarunas", + description: + "Aizvadīt e-pasta sarunas – atzīmēt kā lasītu/neizlasītu, glabāt arhīvā, atrast atkritumos", + }, + account: { + title: "Integrācijas statistika", + description: + "Apspriediet pasta skapja statistiku un konta informāciju.", + }, + }, + skills: { + search: { + title: "Meklē e-pastus", + description: + "Meklēt e-pasta vēstures, izmantojot Gmail meklēšanas sintaksi", + }, + readThread: { + title: "Izlasīt tēmu", + description: "Izlasiet pilnu e-pasta sarunu, izmantojot ID", + }, + createDraft: { + title: "Izveidot izstrādi", + description: "Izveidot jaunu e-pasta rakstu", + }, + createDraftReply: { + title: "Izveidot atbildes projekta", + description: "Izveidot atbildes projekta par esošu tematu.", + }, + updateDraft: { + title: "Pārredzētās versijas", + description: "Atjaunini esošā e-pasta projekta", + }, + getDraft: { + title: "Saņemiet projekta versiju", + description: "Atgūt specifisku dokumentu pēc identifikatora", + }, + listDrafts: { + title: "Pamatdarba projekti", + description: + "Izveidot sarakstu ar visiem izstrādātajiem e-pasta vēstījumiem", + }, + deleteDraft: { + title: "Dzēst projekta versiju", + description: "Dzēst izstrādāto e-pastu", + }, + sendDraft: { + title: "Nosūtīt projekta versiju", + description: "Nosūtiet esošo e-pasta projekta vēstuli", + }, + sendEmail: { + title: "Sūtiet e-pastu", + description: "Sūtiet e-pastu nekavējoties", + }, + replyToThread: { + title: "Atbildēt uz tēmu", + description: "Atbildiet uz e-pasta sarunu nekavējoties", + }, + markRead: { + title: "Mark Reads", + description: "Atzīmējiet tēmu kā lasītu.", + }, + markUnread: { + title: "Mark – neizlasīts", + description: "Atzīmējiet tēmu kā neizlasītu.", + }, + moveToTrash: { + title: "Aizvest uz atkritumu konteineru", + description: "Pārvietojiet tēmu uz atkritumu failu", + }, + moveToArchive: { + title: "Arhivs", + description: "Saglabāt tēmu", + }, + moveToInbox: { + title: 'Pārvietot uz "Ienākošās"', + description: "Pārvietojiet tēmu uz e-pasta skatīšanās rindā", + }, + getMailboxStats: { + title: "Pasta kastes statistika", + description: + "Iesaļojiet neskaitītās e-pasta ziņojumu un e-pasta kastes statistikas", + }, + getInbox: { + title: "Atsvēdināt e-pasta skatīšanās rindu", + description: "Efektīvs veids, kā saņemt e-pastus no Gmail konta", + }, + }, + }, }, mcp: { title: "MCP serveri", diff --git a/frontend/src/locales/nl/common.js b/frontend/src/locales/nl/common.js index 371e7110..aaff73b5 100644 --- a/frontend/src/locales/nl/common.js +++ b/frontend/src/locales/nl/common.js @@ -410,6 +410,126 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail-verbinding", + description: + "Maak het mogelijk voor uw agent om met Gmail te communiceren: e-mails zoeken, threads lezen, e-mails opstellen, e-mails versturen en uw inbox beheren. Bekijk de documentatie.", + multiUserWarning: + "De integratie met Gmail is niet beschikbaar in de modus voor meerdere gebruikers, om veiligheidsredenen. Schakel de modus voor meerdere gebruikers uit om deze functie te gebruiken.", + configuration: "Gmail-instellingen", + deploymentId: "Identificatiecode voor de implementatie", + deploymentIdHelp: "De deployment-ID van je Google Apps Script web-app", + apiKey: "API-sleutel", + apiKeyHelp: + "De API-sleutel die u hebt geconfigureerd in uw Google Apps Script-implementatie.", + configurationRequired: + "Configureer de Deployment ID en de API-sleutel om de Gmail-functionaliteit te activeren.", + configured: "Geconfigureerd", + searchSkills: "Vaardigheden op het gebied van zoeken...", + noSkillsFound: "Geen resultaten die overeenkomen met uw zoekopdracht.", + categories: { + search: { + title: "Zoeken en e-mails lezen", + description: "Zoek en lees e-mails uit uw Gmail inbox.", + }, + drafts: { + title: "Voorbeeld-e-mails", + description: "Maak, bewerk en beheer e-mailontwerpen.", + }, + send: { + title: "Verzenden en antwoorden op e-mails", + description: + "Verzend e-mails en reageer direct op discussieberichten.", + }, + threads: { + title: "Beheer e-mailconversaties", + description: + "Beheer e-mailconversaties – markeer als gelezen/niet gelezen, archiveren, verwijderen", + }, + account: { + title: "Statistieken over integratie", + description: + "Bekijk statistieken en accountinformatie van uw e-mail.", + }, + }, + skills: { + search: { + title: "Zoek naar e-mails", + description: + "Zoek naar e-mails met behulp van de zoeksyntax van Gmail.", + }, + readThread: { + title: "Lees het gesprek", + description: "Lees het volledige e-mailgesprek, gesorteerd op ID.", + }, + createDraft: { + title: "Maak een concept", + description: "Maak een nieuwe concept-e-mail", + }, + createDraftReply: { + title: "Maak een concept-antwoord", + description: "Maak een concept-antwoord op een bestaand gesprek.", + }, + updateDraft: { + title: "Aanpassen: Ontwerp", + description: "Pas een bestaand concept-e-mail aan.", + }, + getDraft: { + title: "Bekijk concept", + description: "Haal een specifiek ontwerp op, gebaseerd op zijn ID.", + }, + listDrafts: { + title: "Ontwerpen", + description: "Maak een lijst van alle concept-e-mails.", + }, + deleteDraft: { + title: "Verwijder concept", + description: "Verwijder een concept-e-mail", + }, + sendDraft: { + title: "Verzend concept", + description: "Verzend een bestaand e-mailconcept.", + }, + sendEmail: { + title: "Stuur e-mail", + description: "Stuur onmiddellijk een e-mail.", + }, + replyToThread: { + title: "Reageer op dit gesprek", + description: "Reageer onmiddellijk op een e-mailthread.", + }, + markRead: { + title: "Mark Read", + description: "Markeer een draad als gelezen", + }, + markUnread: { + title: "Mark ongelezen", + description: "Markeer een bericht als ongelezen.", + }, + moveToTrash: { + title: "Verplaatsen naar prullenbak", + description: "Verplaats een onderwerp naar de prullenbak", + }, + moveToArchive: { + title: "Archief", + description: "Een gesprek archiveren", + }, + moveToInbox: { + title: "Verplaats naar inbox", + description: "Verplaats een bericht naar de inbox", + }, + getMailboxStats: { + title: "Statistieken van de e-mail inbox", + description: + "Bekijk het aantal ongelezen berichten en statistieken over uw e-mailbox.", + }, + getInbox: { + title: "Open de inbox", + description: + "Een eenvoudige manier om de e-mails in uw inbox van Gmail te bekijken.", + }, + }, + }, }, mcp: { title: "MCP-servers", diff --git a/frontend/src/locales/pl/common.js b/frontend/src/locales/pl/common.js index 78c86beb..4e5483d6 100644 --- a/frontend/src/locales/pl/common.js +++ b/frontend/src/locales/pl/common.js @@ -417,6 +417,131 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Połączenie z GMail", + description: + "Pozwól swojemu agentowi na interakcję z Gmail – wyszukiwanie wiadomości e-mail, czytanie wątków, tworzenie projektów, wysyłanie wiadomości e-mail oraz zarządzanie skrzynką odbiorczą. Przeczytaj dokumentację.", + multiUserWarning: + "Integracja z Gmailem nie jest dostępna w trybie wieloosobowym z powodów bezpieczeństwa. Aby korzystać z tej funkcji, należy wyłączyć tryb wieloosobowy.", + configuration: "Konfiguracja Gmaila", + deploymentId: "Identyfikator wdrażania", + deploymentIdHelp: + "ID aplikacji webowej z Google Apps Script, której używasz", + apiKey: "Klucz API", + apiKeyHelp: + "Klucz API, który skonfigurowałeś w swoim projekcie Google Apps Script", + configurationRequired: + "Prosimy o skonfigurowanie identyfikatora wdrażania i klucza API, aby włączyć funkcje związane z Gmail.", + configured: "Skonfigurowany", + searchSkills: "Umiejętności wyszukiwania...", + noSkillsFound: + "Nie znaleziono żadnych kandydatów, którzy spełniałyby Twoje kryteria.", + categories: { + search: { + title: "Wyszukaj i przeczytaj wiadomości e-mail", + description: + "Wyszukaj i przeczytaj e-maile z swojej skrzynki odbiorczej Gmail.", + }, + drafts: { + title: "Proponowane wiadomości e-mail", + description: "Twórz, edytuj i zarządzaj wersjami e-maili.", + }, + send: { + title: "Wysyłanie i odpowiadanie na e-maile", + description: + "Wysyłaj e-maile i odpowiadaj na dyskusje natychmiast.", + }, + threads: { + title: "Zarządzaj wątkami wiadomości e-mail", + description: + "Zarządzaj wątkami e-maili – oznaczaj jako przeczytane/nieprzeczytane, archiwizuj, usuwaj", + }, + account: { + title: "Statystyki dotyczące integracji", + description: + "Przejrzyj statystyki skrzynki pocztowej oraz informacje dotyczące konta.", + }, + }, + skills: { + search: { + title: "Wyszukaj wiadomości", + description: + "Wyszukaj wiadomości e-mail, używając składni zapytań w Gmail.", + }, + readThread: { + title: "Przeczytaj wątek", + description: + "Przeczytaj pełną sekcję korespondencji e-mail według identyfikatora.", + }, + createDraft: { + title: "Utwórz wersję roboczą", + description: "Utwórz nowy projekt wiadomości e-mail", + }, + createDraftReply: { + title: "Stwórz wersję odpowiedzi", + description: "Stwórz wstępną odpowiedź do istniejącego wątku.", + }, + updateDraft: { + title: "Aktualizacja wersji roboczej", + description: "Zaktualizuj istniejący projekt e-maila", + }, + getDraft: { + title: "Otrzymaj wersję roboczą", + description: + "Pobierz konkretny wers dokumentu po jego identyfikatorze.", + }, + listDrafts: { + title: "Proponowane wersje", + description: "Wyświetl wszystkie wersje e-maili.", + }, + deleteDraft: { + title: "Usuń wersję roboczą", + description: "Usuń wersję roboczą wiadomości e-mail", + }, + sendDraft: { + title: "Wyślij wersję roboczą", + description: "Wyślij istniejący projekt wiadomości e-mail", + }, + sendEmail: { + title: "Wyślij e-mail", + description: "Wyślij e-mail natychmiast", + }, + replyToThread: { + title: "Odpowiedź na wątek", + description: "Odpowiedz na wątek wiadomości e-mail natychmiast", + }, + markRead: { + title: "Mark Read", + description: "Oznacz wątek jako przeczytany", + }, + markUnread: { + title: "Oznacz jako nieprzeczytane", + description: "Oznacz wątek jako nieprzeczytany", + }, + moveToTrash: { + title: "Przenieś do kosza", + description: "Przenieś wątek do kosza", + }, + moveToArchive: { + title: "Archiwum", + description: "Zarchiwizuj wątek", + }, + moveToInbox: { + title: 'Przenieś do folderu "Otrzymane"', + description: "Przenieś wątek do folderu „Ostatnie wiadomości”", + }, + getMailboxStats: { + title: "Statystyki skrzynki pocztowej", + description: + "Uzyskaj informacje o liczbie nieprzeczytanych wiadomości oraz statystyki dotyczące skrzynki pocztowej.", + }, + getInbox: { + title: "Otwórz skrzynkę odbiorczą", + description: + "Sprawne rozwiązanie, dzięki któremu można łatwo pobierać wiadomości z skrzynki odbiorczej z Gmaila.", + }, + }, + }, }, mcp: { title: "Serwery MCP", diff --git a/frontend/src/locales/pt_BR/common.js b/frontend/src/locales/pt_BR/common.js index 52ae9c3a..f3040d2c 100644 --- a/frontend/src/locales/pt_BR/common.js +++ b/frontend/src/locales/pt_BR/common.js @@ -415,6 +415,130 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Conector do GMail", + description: + "Permita que seu agente interaja com o Gmail – pesquise e-mails, leia conversas, crie rascunhos, envie e-mails e gerencie sua caixa de entrada. Consulte a documentação.", + multiUserWarning: + "A integração com o Gmail não está disponível no modo multiusuário, por razões de segurança. Por favor, desative o modo multiusuário para utilizar esta funcionalidade.", + configuration: "Configuração do Gmail", + deploymentId: "ID de implantação", + deploymentIdHelp: + "O ID de implantação da sua aplicação web do Google Apps Script", + apiKey: "Chave de API", + apiKeyHelp: + "A chave de API que você configurou no seu projeto Google Apps Script", + configurationRequired: + "Por favor, configure o ID de Implantação e a Chave de API para habilitar as funcionalidades do Gmail.", + configured: "Configurado", + searchSkills: "Habilidades de pesquisa...", + noSkillsFound: + "Não encontramos nenhum resultado que corresponda à sua pesquisa.", + categories: { + search: { + title: "Pesquisar e ler e-mails", + description: + "Pesquise e leia e-mails da sua caixa de entrada do Gmail.", + }, + drafts: { + title: "Rascunhos de e-mails", + description: "Crie, edite e gerencie rascunhos de e-mails.", + }, + send: { + title: "Enviar e responder a e-mails", + description: "Envie e-mails e responda a discussões imediatamente.", + }, + threads: { + title: "Gerenciar Fios de E-mail", + description: + "Gerenciar threads de e-mail: marcar como lido/não lido, arquivar, excluir.", + }, + account: { + title: "Estatísticas de integração", + description: + "Visualize estatísticas da caixa de correio e informações da conta.", + }, + }, + skills: { + search: { + title: "Pesquisar e-mails", + description: + "Pesquise e-mails usando a sintaxe de consulta do Gmail.", + }, + readThread: { + title: "Leia a discussão", + description: "Leia toda a sequência de e-mails por ID.", + }, + createDraft: { + title: "Criar rascunho", + description: "Crie uma nova versão do e-mail.", + }, + createDraftReply: { + title: "Criar resposta preliminar", + description: + "Crie uma resposta preliminar a um tópico já existente.", + }, + updateDraft: { + title: "Versão atualizada", + description: "Atualizar um rascunho de e-mail existente", + }, + getDraft: { + title: "Obter rascunho", + description: "Recuperar uma versão específica por ID", + }, + listDrafts: { + title: "Rascunhos", + description: "Liste todos os e-mails em rascunho.", + }, + deleteDraft: { + title: "Excluir rascunho", + description: "Excluir um rascunho de e-mail", + }, + sendDraft: { + title: "Enviar rascunho", + description: "Envie uma versão prévia de um e-mail existente.", + }, + sendEmail: { + title: "Enviar e-mail", + description: "Envie um e-mail imediatamente.", + }, + replyToThread: { + title: "Responder à discussão", + description: + "Responder a uma discussão por e-mail o mais rápido possível.", + }, + markRead: { + title: "Mark Read", + description: "Marque um tópico como lido", + }, + markUnread: { + title: "Marcar como não lido", + description: "Marque um tópico como não lido", + }, + moveToTrash: { + title: "Mover para a lixeira", + description: "Mover um tópico para a lixeira", + }, + moveToArchive: { + title: "Arquivo", + description: "Arquivar um tópico", + }, + moveToInbox: { + title: "Mover para a caixa de entrada", + description: "Mova a discussão para a caixa de entrada", + }, + getMailboxStats: { + title: "Estatísticas da caixa de correio", + description: + "Obtenha o número de e-mails não lidos e estatísticas da caixa de correio.", + }, + getInbox: { + title: "Acessar a caixa de entrada", + description: + "Uma maneira eficiente de acessar os e-mails na caixa de entrada do Gmail.", + }, + }, + }, }, mcp: { title: "Servidores MCP", diff --git a/frontend/src/locales/ro/common.js b/frontend/src/locales/ro/common.js index 9248a956..928a86d0 100644 --- a/frontend/src/locales/ro/common.js +++ b/frontend/src/locales/ro/common.js @@ -866,6 +866,129 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Conectorul GMail", + description: + "Permite-i agentului tău să interacționeze cu Gmail: caută e-mailuri, citește conversații, redactează proiecte, trimite e-mailuri și gestionează folderul tău de e-mail. Consultă documentația disponibilă aici.", + multiUserWarning: + "Integrarea cu Gmail nu este disponibilă în modul multi-utilizator, din motive de securitate. Vă rugăm să dezactivați modul multi-utilizator pentru a utiliza această funcție.", + configuration: "Configurarea contului Gmail", + deploymentId: "Identificator de implementare", + deploymentIdHelp: + "ID-ul de implementare al aplicației web Google Apps Script", + apiKey: "Cheie API", + apiKeyHelp: + "Cheia API pe care ați configurat în mediul de implementare Google Apps Script", + configurationRequired: + "Vă rugăm să configurați ID-ul de implementare și cheia API pentru a activa funcționalitățile Gmail.", + configured: "Configurat", + searchSkills: "Abilități de căutare...", + noSkillsFound: + "Nu s-au găsit rezultate care să corespundă criteriilor dumneavoastră de căutare.", + categories: { + search: { + title: "Căutați și citiți e-mailuri", + description: + "Căutați și citiți e-mailuri din folderul dumneavoastră Gmail", + }, + drafts: { + title: "Propuneri de e-mail", + description: "Creați, editați și gestionați schițele de e-mail.", + }, + send: { + title: "Trimite și răspunde la e-mailuri", + description: "Trimite e-mailuri și răspunde imediat la discuții", + }, + threads: { + title: "Gestionați conversațiile prin e-mail", + description: + "Gestionați corespondența prin e-mail: marcați ca fiind citite/nepuse în evidență, arhivați, eliminați", + }, + account: { + title: "Statistici privind integrarea", + description: + "Vizualizați statistici privind cutia poștală și informații despre cont", + }, + }, + skills: { + search: { + title: "Căutați în e-mailuri", + description: + "Căutați în e-mailuri folosind sintaxa de interogare a Gmail", + }, + readThread: { + title: "Citește thread-ul", + description: + "Citește întregul fir de e-mail, folosind un identificator (ID).", + }, + createDraft: { + title: "Creează o schiță", + description: "Creați un proiect nou de e-mail", + }, + createDraftReply: { + title: "Creează un răspuns preliminar", + description: "Creați un proiect de răspuns la un thread existent.", + }, + updateDraft: { + title: "Actualizare proiect", + description: "Actualizați un e-mail existent, draft", + }, + getDraft: { + title: "Obține versiunea preliminară", + description: "Recuperați un proiect specific folosind ID-ul său", + }, + listDrafts: { + title: "Propuneri", + description: "Enumerați toate e-mailurile draft", + }, + deleteDraft: { + title: "Șterge proiectul", + description: "Șterge un proiect de email", + }, + sendDraft: { + title: "Trimite versiunea preliminară", + description: "Trimiteți o versiune existentă a unui e-mail", + }, + sendEmail: { + title: "Trimite e-mail", + description: "Trimiteți un e-mail imediat.", + }, + replyToThread: { + title: "Răspunde la discuție", + description: "Răspundeți imediat la un fir de e-mail", + }, + markRead: { + title: "Mark Read", + description: "Marcați un fir ca fiind citit", + }, + markUnread: { + title: "Marchează ca necitit", + description: "Marcați un thread ca fiind necitit", + }, + moveToTrash: { + title: "Mută în coșul de gunoi", + description: "Mută un fir în coșul de gunoi", + }, + moveToArchive: { + title: "Arhivă", + description: "Arhivează un thread", + }, + moveToInbox: { + title: "Mută în Inbox", + description: "Mută un fir în folderul „Intrări”", + }, + getMailboxStats: { + title: "Statistici cutie poștală", + description: + "Obține numărul de e-mailuri necitite și statistici privind cutia poștală.", + }, + getInbox: { + title: 'Accesează folderul "Inbox"', + description: + "O modalitate eficientă de a accesa e-mailurile din inbox-ul Gmail.", + }, + }, + }, }, mcp: { title: "Servere MCP", diff --git a/frontend/src/locales/ru/common.js b/frontend/src/locales/ru/common.js index e28edbc5..55bfc63b 100644 --- a/frontend/src/locales/ru/common.js +++ b/frontend/src/locales/ru/common.js @@ -409,6 +409,130 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Подключение к GMail", + description: + "Предоставьте своему агенту возможность взаимодействовать с Gmail: искать письма, читать цепочки сообщений, составлять черновики, отправлять письма и управлять своей почтой. Ознакомьтесь с документацией.", + multiUserWarning: + "Интеграция с Gmail недоступна в многопользовательском режиме по соображениям безопасности. Пожалуйста, отключите многопользовательский режим, чтобы использовать эту функцию.", + configuration: "Настройка Gmail", + deploymentId: "Идентификатор развертывания", + deploymentIdHelp: + "Идентификатор развертывания вашего веб-приложения Google Apps Script", + apiKey: "Ключ API", + apiKeyHelp: + "Ключ API, который вы настроили в своем проекте Google Apps Script", + configurationRequired: + "Пожалуйста, настройте идентификатор развертывания и API-ключ, чтобы активировать функции Gmail.", + configured: "Настроен", + searchSkills: "Навыки поиска...", + noSkillsFound: "Не найдено ни одного совпадения с вашим запросом.", + categories: { + search: { + title: "Поиск и чтение электронных писем", + description: + "Найдите и прочитайте электронные письма из вашей почты Gmail.", + }, + drafts: { + title: "Черновики электронных писем", + description: + "Создавайте, редактируйте и управляйте черновиками электронных писем.", + }, + send: { + title: "Отправка и ответы на электронные письма", + description: + "Отправляйте электронные письма и оперативно отвечайте на сообщения в обсуждениях.", + }, + threads: { + title: "Управление цепочками писем", + description: + "Управление цепочками электронных писем: пометка как прочитанные/непрочитанные, архивирование, удаление", + }, + account: { + title: "Статистика интеграции", + description: + "Просмотрите статистику использования почтового ящика и информацию об учетной записи.", + }, + }, + skills: { + search: { + title: "Поиск в электронных письмах", + description: "Искать письма, используя синтаксис запросов Gmail", + }, + readThread: { + title: "Прочитать тему", + description: + "Просмотрите всю цепочку электронных писем, отсортированную по идентификатору.", + }, + createDraft: { + title: "Создать черновик", + description: "Создайте новый черновик электронного письма", + }, + createDraftReply: { + title: "Создать черновик ответа", + description: + "Составьте черновик ответа к существующему обсуждению.", + }, + updateDraft: { + title: "Обновленная версия проекта", + description: "Обновить существующий черновик электронного письма", + }, + getDraft: { + title: "Получить черновик", + description: "Получить конкретный черновик по его идентификатору", + }, + listDrafts: { + title: "Черновики", + description: "Перечислите все черновики электронных писем.", + }, + deleteDraft: { + title: "Удалить черновик", + description: "Удалить черновик письма", + }, + sendDraft: { + title: "Отправить черновик", + description: "Отправить существующий черновик электронного письма", + }, + sendEmail: { + title: "Отправить электронное письмо", + description: "Немедленно отправьте электронное письмо.", + }, + replyToThread: { + title: "Ответить на тему", + description: "Ответить на цепочку писем немедленно", + }, + markRead: { + title: "Марк Рид", + description: "Отметьте тему как прочитанную.", + }, + markUnread: { + title: "Отмечено как непрочитанное", + description: "Отметьте тему как непрочитанную.", + }, + moveToTrash: { + title: "Переместить в корзину", + description: "Переместить тему в корзину", + }, + moveToArchive: { + title: "Архив", + description: "Архивировать тему", + }, + moveToInbox: { + title: 'Переместить в папку "Входящие"', + description: "Переместить тему в папку «Входящие»", + }, + getMailboxStats: { + title: "Статистика почтового ящика", + description: + "Получите информацию о количестве непрочитанных писем и статистику почтового ящика.", + }, + getInbox: { + title: "Открыть входящие", + description: + "Удобный способ получения писем из почтового ящика Gmail.", + }, + }, + }, }, mcp: { title: "Серверы MCP", diff --git a/frontend/src/locales/tr/common.js b/frontend/src/locales/tr/common.js index 620812da..ef892514 100644 --- a/frontend/src/locales/tr/common.js +++ b/frontend/src/locales/tr/common.js @@ -410,6 +410,131 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "GMail Bağlantısı", + description: + "Ajantınızın Gmail ile etkileşim kurmasını sağlayın: e-postaları aratın, sohbetleri okuyun, taslaklar oluşturun, e-postalar gönderin ve posta kutunuzu yönetin. Dokümantasyonu okuyun.", + multiUserWarning: + "Gmail entegrasyonu, güvenlik nedenlerinden dolayı çoklu kullanıcı modunda mevcut değildir. Bu özelliği kullanmak için lütfen çoklu kullanıcı modunu devre dışı bırakın.", + configuration: "Gmail Yapılandırma", + deploymentId: "Dağıtım Kimliği", + deploymentIdHelp: + "Google Apps Script web uygulamanızın uygulama kimliği", + apiKey: "API Anahtarı", + apiKeyHelp: + "Google Apps Script kurulumunuzda yapılandırdığınız API anahtarı", + configurationRequired: + "Lütfen Dağıtım Kimliğini ve API Anahtarını yapılandırarak Gmail yeteneklerini etkinleştirin.", + configured: "Yapılandırılmış", + searchSkills: "Arama becerileri...", + noSkillsFound: + "Belirttiğiniz kriterlere uyan herhangi bir sonuç bulunamadı.", + categories: { + search: { + title: "E-postaları arayın ve okuyun", + description: "Gmail hesabınızdaki e-postaları arayın ve okuyun.", + }, + drafts: { + title: "Taslak E-postalar", + description: + "E-posta taslaklarını oluşturun, düzenleyin ve yönetin.", + }, + send: { + title: "E-postaları gönderme ve yanıt verme", + description: + "E-postalar gönderin ve tartışmalara anında yanıt verin.", + }, + threads: { + title: "E-posta dizilerini yönetin", + description: + "E-posta dizilerini yönetin - okundu/okunmadı olarak işaretleme, arşivleme, çöp kutusuna gönderme", + }, + account: { + title: "Entegrasyon İstatistikleri", + description: + "Post kutunuzdaki istatistikleri ve hesap bilgilerinizi görüntüleyin.", + }, + }, + skills: { + search: { + title: "E-postaları arayın", + description: "Gmail sorgu sözdizimi kullanarak e-postaları arayın", + }, + readThread: { + title: "Makaleyi oku", + description: + "ID numarasına göre, e-posta iletişimin bütününü okuyun.", + }, + createDraft: { + title: "Taslak Oluştur", + description: "Yeni bir e-posta taslağı oluşturun", + }, + createDraftReply: { + title: "Taslak Yanıt Oluştur", + description: + "Mevcut bir tartışma başlığına yönelik bir yanıt taslağı oluşturun.", + }, + updateDraft: { + title: "Taslak Güncelleme", + description: "Mevcut bir e-posta taslağını güncelleyin", + }, + getDraft: { + title: "Taslakları görüntüle", + description: "Belirli bir taslağı, ID numarası ile alın", + }, + listDrafts: { + title: "Taslaklar", + description: "Tüm taslak e-postaları listele", + }, + deleteDraft: { + title: "Taslağı Sil", + description: "Bir taslak e-postayı sil", + }, + sendDraft: { + title: "Taslak Gönder", + description: "Mevcut bir e-posta taslağını gönderin", + }, + sendEmail: { + title: "E-posta gönder", + description: "Hemen bir e-posta gönderin", + }, + replyToThread: { + title: "Konuya Cevap Ver", + description: "Bir e-posta dizisine anında yanıt verin", + }, + markRead: { + title: "মার্ক रीड", + description: "Bir konuyla ilgili mesajları okundu olarak işaretle.", + }, + markUnread: { + title: "Okunmadı olarak işaretle", + description: 'Bir gönderiyi "okunmamış" olarak işaretle.', + }, + moveToTrash: { + title: "Çöp kutusuna taşı", + description: "Bir başlığı çöp kutusuna taşı.", + }, + moveToArchive: { + title: "Arşiv", + description: "Bir konuyu arşivle", + }, + moveToInbox: { + title: "Gelen kutusuna taşı", + description: + "Bir konuyla ilgili mesajları ana posta kutusuna taşı.", + }, + getMailboxStats: { + title: "Posta Kutusu İstatistikleri", + description: + "Okunmamış mesaj sayılarını ve posta kutusu istatistiklerini görüntüleyin.", + }, + getInbox: { + title: "Gelen kutusuna eriş", + description: + "Gmail'den gelen e-postaları hızlı ve kolay bir şekilde görüntüleme yolu.", + }, + }, + }, }, mcp: { title: "MCP Sunucuları", diff --git a/frontend/src/locales/vn/common.js b/frontend/src/locales/vn/common.js index ab9fcbd3..94b20480 100644 --- a/frontend/src/locales/vn/common.js +++ b/frontend/src/locales/vn/common.js @@ -408,6 +408,125 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Kết nối GMail", + description: + "Cho phép đại lý của bạn tương tác với Gmail – tìm kiếm email, đọc các cuộc trò chuyện, soạn thảo bản nháp, gửi email và quản lý hộp thư. Đọc tài liệu hướng dẫn.", + multiUserWarning: + "Tính năng tích hợp với Gmail không khả dụng trong chế độ nhiều người dùng vì lý do bảo mật. Vui lòng tắt chế độ nhiều người dùng để sử dụng tính năng này.", + configuration: "Cấu hình Gmail", + deploymentId: "Mã triển khai", + deploymentIdHelp: + "Mã triển khai từ ứng dụng web Google Apps Script của bạn", + apiKey: "Khóa API", + apiKeyHelp: + "Khóa API mà bạn đã cấu hình trong quá trình triển khai Google Apps Script của mình.", + configurationRequired: + "Vui lòng cấu hình ID triển khai và khóa API để kích hoạt các tính năng của Gmail.", + configured: "Đã được cấu hình", + searchSkills: "Kỹ năng tìm kiếm...", + noSkillsFound: "Không có kết quả phù hợp với tìm kiếm của bạn.", + categories: { + search: { + title: "Tìm kiếm và đọc email", + description: + "Tìm kiếm và đọc các email trong hộp thư Gmail của bạn.", + }, + drafts: { + title: "Mẫu email", + description: "Tạo, chỉnh sửa và quản lý bản nháp email", + }, + send: { + title: "Gửi và trả lời email", + description: + "Gửi email và trả lời các cuộc thảo luận ngay lập tức.", + }, + threads: { + title: "Quản lý các chuỗi email", + description: + "Quản lý các chuỗi email – đánh dấu là đã đọc/chưa đọc, lưu trữ, xóa", + }, + account: { + title: "Thống kê tích hợp", + description: "Xem thống kê hộp thư và thông tin tài khoản", + }, + }, + skills: { + search: { + title: "Tìm kiếm trong các email", + description: "Tìm kiếm trong email bằng cú pháp truy vấn của Gmail", + }, + readThread: { + title: "Đọc chủ đề", + description: "Đọc toàn bộ chuỗi email theo ID", + }, + createDraft: { + title: "Tạo bản nháp", + description: "Tạo một bản nháp email mới", + }, + createDraftReply: { + title: "Tạo bản nháp trả lời", + description: "Tạo một bản dự thảo trả lời cho một chủ đề hiện có.", + }, + updateDraft: { + title: "Cập nhật bản nháp", + description: "Cập nhật bản nháp email hiện có", + }, + getDraft: { + title: "Xem bản nháp", + description: "Lấy lại một bản nháp cụ thể theo ID", + }, + listDrafts: { + title: "Danh sách dự thảo", + description: "Liệt kê tất cả các email đang soạn thảo.", + }, + deleteDraft: { + title: "Xóa bản nháp", + description: "Xóa bản nháp email", + }, + sendDraft: { + title: "Gửi bản nháp", + description: "Gửi một bản nháp email đã có", + }, + sendEmail: { + title: "Gửi email", + description: "Gửi một email ngay lập tức", + }, + replyToThread: { + title: "Trả lời cuộc thảo luận", + description: "Trả lời một chuỗi email ngay lập tức.", + }, + markRead: { + title: "Mark Read", + description: "Đánh dấu một chủ đề đã đọc", + }, + markUnread: { + title: "Đánh dấu là chưa đọc", + description: "Đánh dấu một chủ đề là chưa đọc", + }, + moveToTrash: { + title: "Xóa", + description: 'Di chuyển một chủ đề vào thư mục "rác"', + }, + moveToArchive: { + title: "Thư mục lưu trữ", + description: "Lưu trữ một chủ đề", + }, + moveToInbox: { + title: "Di chuyển đến mục Thùng thư", + description: "Di chuyển một chủ đề vào hộp thư mục", + }, + getMailboxStats: { + title: "Thống kê hộp thư", + description: + "Hiển thị số lượng email chưa đọc và thống kê hộp thư.", + }, + getInbox: { + title: "Truy cập hộp thư", + description: "Cách đơn giản để lấy các email trong hộp thư Gmail.", + }, + }, + }, }, mcp: { title: "Máy chủ MCP", diff --git a/frontend/src/locales/zh/common.js b/frontend/src/locales/zh/common.js index 51968453..2527a093 100644 --- a/frontend/src/locales/zh/common.js +++ b/frontend/src/locales/zh/common.js @@ -394,6 +394,118 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Gmail 连接器", + description: + "让您的代理能够与Gmail互动:搜索邮件、阅读邮件线程、撰写草稿、发送邮件以及管理您的收件箱。请参考相关文档。", + multiUserWarning: + "为了安全原因,在多用户模式下无法使用 Gmail 集成功能。请先禁用多用户模式,然后才能使用此功能。", + configuration: "Gmail 设置", + deploymentId: "部署 ID", + deploymentIdHelp: "您的 Google Apps Script 网页应用的部署 ID", + apiKey: "API 密钥", + apiKeyHelp: "您在 Google Apps Script 部署中配置的 API 密钥。", + configurationRequired: "请配置部署 ID 和 API 密钥,以启用 Gmail 功能。", + configured: "已配置", + searchSkills: "搜索技巧...", + noSkillsFound: "未找到与您的搜索条件匹配的技能。", + categories: { + search: { + title: "搜索和阅读电子邮件", + description: "搜索并阅读您 Gmail 收件箱中的邮件。", + }, + drafts: { + title: "草稿邮件", + description: "创建、编辑和管理电子邮件草稿", + }, + send: { + title: "发送和回复电子邮件", + description: "立即发送电子邮件并回复讨论串", + }, + threads: { + title: "管理电子邮件线程", + description: "管理邮件线程 - 标记为已读/未读,归档,删除", + }, + account: { + title: "集成统计", + description: "查看邮件收件箱统计数据和账户信息", + }, + }, + skills: { + search: { + title: "搜索邮件", + description: "使用 Gmail 的查询语法搜索电子邮件", + }, + readThread: { + title: "阅读此主题", + description: "阅读由ID发起的完整邮件往来", + }, + createDraft: { + title: "创建草稿", + description: "创建一个新的电子邮件草稿", + }, + createDraftReply: { + title: "创建草稿回复", + description: "创建一个针对现有主题的回应草稿", + }, + updateDraft: { + title: "更新草稿", + description: "更新已有的电子邮件草稿", + }, + getDraft: { + title: "获取草稿", + description: "通过ID检索特定草稿", + }, + listDrafts: { + title: "草稿清单", + description: "列出所有草稿邮件", + }, + deleteDraft: { + title: "删除草稿", + description: "删除草稿邮件", + }, + sendDraft: { + title: "发送草稿", + description: "发送已有的电子邮件草稿", + }, + sendEmail: { + title: "发送电子邮件", + description: "立即发送一封电子邮件", + }, + replyToThread: { + title: "回复主题", + description: "立即回复邮件线程", + }, + markRead: { + title: "马克·瑞德", + description: "将某个主题标记为已阅读", + }, + markUnread: { + title: "标记为未读", + description: "将某个主题标记为未读", + }, + moveToTrash: { + title: "移动到垃圾箱", + description: "将某个主题归档到垃圾箱", + }, + moveToArchive: { + title: "存档", + description: "存档该主题", + }, + moveToInbox: { + title: "移动到收件箱", + description: "将某个主题移动到收件箱", + }, + getMailboxStats: { + title: "邮箱统计", + description: "获取未读邮件数量和邮箱统计信息", + }, + getInbox: { + title: "查看收件箱", + description: "一种便捷的方式,可以从 Gmail 中获取收件邮件。", + }, + }, + }, }, mcp: { title: "MCP 服务器", diff --git a/frontend/src/locales/zh_TW/common.js b/frontend/src/locales/zh_TW/common.js index 4bae1819..eac19360 100644 --- a/frontend/src/locales/zh_TW/common.js +++ b/frontend/src/locales/zh_TW/common.js @@ -384,6 +384,118 @@ const TRANSLATIONS = { }, }, }, + gmail: { + title: "Gmail 連接器", + description: + "讓您的代理能夠與 Gmail 互動:搜尋郵件、閱讀郵件討論、撰寫草稿、發送郵件,以及管理您的收件匣。請參閱相關文件。", + multiUserWarning: + "由於安全考量,Gmail 整合功能在多使用者模式下無法使用。請停用多使用者模式才能使用此功能。", + configuration: "Gmail 設定", + deploymentId: "部署 ID", + deploymentIdHelp: "您的 Google Apps Script 網頁應用程式的部署 ID", + apiKey: "API 關鍵字", + apiKeyHelp: "您在 Google Apps Script 部署中設定的 API 金鑰", + configurationRequired: "請設定部署 ID 和 API 關鍵,以啟用 Gmail 功能。", + configured: "設定", + searchSkills: "搜尋技巧...", + noSkillsFound: "沒有符合您搜尋條件的結果。", + categories: { + search: { + title: "搜尋和閱讀電子郵件", + description: "搜尋並閱讀您 Gmail 收件匣中的電子郵件。", + }, + drafts: { + title: "草稿郵件", + description: "創建、編輯和管理電子郵件草稿", + }, + send: { + title: "發送和回覆電子郵件", + description: "立即發送電子郵件並回覆討論串", + }, + threads: { + title: "管理電子郵件串", + description: "管理電子郵件對話 - 標示已讀/未讀、歸檔、刪除", + }, + account: { + title: "整合統計", + description: "查看郵箱統計資料和帳戶資訊", + }, + }, + skills: { + search: { + title: "搜尋郵件", + description: "使用 Gmail 的查詢語法搜尋電子郵件", + }, + readThread: { + title: "閱讀主題", + description: "閱讀由 ID 創建的完整電子郵件對話", + }, + createDraft: { + title: "建立草稿", + description: "建立新的電子郵件草稿", + }, + createDraftReply: { + title: "撰寫草稿回覆", + description: "撰寫一份針對已有的討論串的回覆草稿。", + }, + updateDraft: { + title: "更新草稿", + description: "更新現有電子郵件草稿", + }, + getDraft: { + title: "取得草稿", + description: "根據 ID 取得特定草稿", + }, + listDrafts: { + title: "草稿清單", + description: "列出所有草稿電子郵件", + }, + deleteDraft: { + title: "刪除草稿", + description: "刪除草稿電子郵件", + }, + sendDraft: { + title: "發送草稿", + description: "發送現有電子郵件草稿", + }, + sendEmail: { + title: "發送電子郵件", + description: "立即發送電子郵件", + }, + replyToThread: { + title: "回覆主題", + description: "立即回覆電子郵件討論串", + }, + markRead: { + title: "馬克·瑞德", + description: "標示某個主題已閱讀", + }, + markUnread: { + title: "標示為未讀", + description: "將某個主題標示為未讀", + }, + moveToTrash: { + title: "移至垃圾桶", + description: "將主題移動到垃圾桶", + }, + moveToArchive: { + title: "檔案", + description: "將主題歸檔", + }, + moveToInbox: { + title: "移動到收件匣", + description: "將主題移動到收件匣", + }, + getMailboxStats: { + title: "郵箱統計", + description: "查看未讀郵件數量及郵箱統計數據", + }, + getInbox: { + title: "開啟收件匣", + description: "簡潔的方式,讓您能輕鬆取得 Gmail 郵箱中的郵件。", + }, + }, + }, }, mcp: { title: "MCP 伺服器", diff --git a/frontend/src/pages/Admin/Agents/GMailSkillPanel/gmail.png b/frontend/src/pages/Admin/Agents/GMailSkillPanel/gmail.png new file mode 100644 index 0000000000000000000000000000000000000000..2beed80336cbf8265ed2c729daab704e2fea1167 GIT binary patch literal 21933 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 VEhT{m{HGzQ9`v5phuc=c{{hag;jRDx literal 0 HcmV?d00001 diff --git a/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx b/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx new file mode 100644 index 00000000..a9809274 --- /dev/null +++ b/frontend/src/pages/Admin/Agents/GMailSkillPanel/index.jsx @@ -0,0 +1,443 @@ +import React, { + useEffect, + useState, + useRef, + useMemo, + useCallback, +} from "react"; +import Toggle, { SimpleToggleSwitch } from "@/components/lib/Toggle"; +import { Trans, useTranslation } from "react-i18next"; +import debounce from "lodash.debounce"; +import { + MagnifyingGlass, + CircleNotch, + Warning, + CaretDown, + CheckCircle, + Info, +} from "@phosphor-icons/react"; +import GMailIcon from "./gmail.png"; +import Admin from "@/models/admin"; +import System from "@/models/system"; +import { getGmailSkills, filterSkillCategories } from "./utils"; +import { Tooltip } from "react-tooltip"; +import { Link } from "react-router-dom"; +import paths from "@/utils/paths"; + +export default function GMailSkillPanel({ + title, + skill, + toggleSkill, + enabled = false, + disabled = false, + setHasChanges, + hasChanges = false, +}) { + const { t } = useTranslation(); + const [disabledSkills, setDisabledSkills] = useState([]); + const [loading, setLoading] = useState(true); + const [deploymentId, setDeploymentId] = useState(""); + const [apiKey, setApiKey] = useState(""); + const [isMultiUserMode, setIsMultiUserMode] = useState(false); + const [configDefaultExpanded, setConfigDefaultExpanded] = useState(true); + const prevHasChanges = useRef(hasChanges); + const skillCategories = getGmailSkills(t); + + useEffect(() => { + setLoading(true); + Promise.all([ + Admin.systemPreferencesByFields([ + "disabled_gmail_skills", + "gmail_deployment_id", + "gmail_api_key", + ]), + System.keys(), + ]) + .then(([prefsRes, settingsRes]) => { + const loadedDeploymentId = + prefsRes?.settings?.gmail_deployment_id ?? ""; + const loadedApiKey = prefsRes?.settings?.gmail_api_key ?? ""; + setDisabledSkills(prefsRes?.settings?.disabled_gmail_skills ?? []); + setDeploymentId(loadedDeploymentId); + setApiKey(loadedApiKey); + setIsMultiUserMode(settingsRes?.MultiUserMode ?? false); + setConfigDefaultExpanded(!(loadedDeploymentId && loadedApiKey)); + }) + .catch(() => { + setDisabledSkills([]); + setDeploymentId(""); + setApiKey(""); + }) + .finally(() => setLoading(false)); + }, []); + + useEffect(() => { + if (prevHasChanges.current === true && hasChanges === false) { + Admin.systemPreferencesByFields([ + "disabled_gmail_skills", + "gmail_deployment_id", + "gmail_api_key", + ]) + .then((res) => { + setDisabledSkills(res?.settings?.disabled_gmail_skills ?? []); + setDeploymentId(res?.settings?.gmail_deployment_id ?? ""); + setApiKey(res?.settings?.gmail_api_key ?? ""); + }) + .catch(() => {}); + } + prevHasChanges.current = hasChanges; + }, [hasChanges]); + + function toggleGmailSkill(skillName) { + setHasChanges(true); + setDisabledSkills((prev) => + prev.includes(skillName) + ? prev.filter((s) => s !== skillName) + : [...prev, skillName] + ); + } + + const isConfigured = deploymentId && apiKey; + + return ( +
+
+
+
+ GMail + +
+ toggleSkill(skill)} + /> +
+ + {isMultiUserMode && ( +
+ +

+ {t("agent.skill.gmail.multiUserWarning")} +

+
+ )} + +

+ + ), + }} + /> +

+ + {enabled && !isMultiUserMode && ( + <> + + + {loading ? ( +
+ +
+ ) : ( + <> + + + {isConfigured && ( + + )} + + )} + + )} +
+
+ ); +} + +function ConfigurationSection({ + deploymentId, + setDeploymentId, + apiKey, + setApiKey, + setHasChanges, + isConfigured, + defaultExpanded = true, +}) { + const { t } = useTranslation(); + const [expanded, setExpanded] = useState(defaultExpanded); + + return ( +
+ + + {expanded && ( +
+
+
+ + + + {t("agent.skill.gmail.deploymentIdHelp")} + +
+ { + setDeploymentId(e.target.value); + setHasChanges(true); + }} + placeholder="AKfycb..." + className="w-full px-3 py-2 bg-theme-bg-primary border border-theme-sidebar-border rounded-lg text-theme-text-primary text-sm placeholder:text-theme-text-secondary/50" + /> +
+ +
+
+ + + + {t("agent.skill.gmail.apiKeyHelp")} + +
+ { + setApiKey(e.target.value); + setHasChanges(true); + }} + placeholder="Your API key..." + className="w-full px-3 py-2 bg-theme-bg-primary border border-theme-sidebar-border rounded-lg text-theme-text-primary text-sm placeholder:text-theme-text-secondary/50" + /> +
+ {!isConfigured && ( +
+ +

+ {t("agent.skill.gmail.configurationRequired")} +

+
+ )} +
+ )} +
+ ); +} + +function SkillSearchInput({ onSearch }) { + const { t } = useTranslation(); + const inputRef = useRef(null); + + const debouncedSearch = useMemo( + () => + debounce((value) => { + onSearch(value); + }, 300), + [onSearch] + ); + + useEffect(() => { + return () => { + debouncedSearch.cancel(); + }; + }, [debouncedSearch]); + + const handleChange = (e) => { + debouncedSearch(e.target.value); + }; + + return ( +
+ + +
+ ); +} + +function SkillsSection({ skillCategories, disabledSkills, onToggle }) { + const { t } = useTranslation(); + const [searchTerm, setSearchTerm] = useState(""); + + const handleSearch = useCallback((value) => { + setSearchTerm(value); + }, []); + + const filteredCategories = useMemo( + () => filterSkillCategories(skillCategories, searchTerm), + [skillCategories, searchTerm] + ); + + const hasResults = Object.keys(filteredCategories).length > 0; + + return ( +
+ + {hasResults ? ( +
+ {Object.entries(filteredCategories).map(([categoryKey, category]) => ( + + ))} +
+ ) : ( +

+ {t("agent.skill.gmail.noSkillsFound")} +

+ )} +
+ ); +} + +function CategorySection({ category, disabledSkills, onToggle }) { + const Icon = category.icon; + + return ( +
+
+ + + {category.title} + +
+
+ {category.skills.map((skill) => ( + onToggle(skill.name)} + /> + ))} +
+
+ ); +} + +function SkillRow({ skill, disabled, onToggle }) { + return ( +
+
+ + {skill.title} + + + {skill.description} + +
+ +
+ ); +} + +function HiddenFormInputs({ disabledSkills, deploymentId, apiKey }) { + return ( + <> + + + + + ); +} diff --git a/frontend/src/pages/Admin/Agents/GMailSkillPanel/utils.js b/frontend/src/pages/Admin/Agents/GMailSkillPanel/utils.js new file mode 100644 index 00000000..ea683adc --- /dev/null +++ b/frontend/src/pages/Admin/Agents/GMailSkillPanel/utils.js @@ -0,0 +1,168 @@ +import { + MagnifyingGlass, + EnvelopeOpen, + PaperPlaneTilt, + ChartBar, + PencilSimple, +} from "@phosphor-icons/react"; +import strDistance from "js-levenshtein"; + +const LEVENSHTEIN_THRESHOLD = 3; + +export const getGmailSkills = (t) => ({ + search: { + title: t("agent.skill.gmail.categories.search.title"), + description: t("agent.skill.gmail.categories.search.description"), + icon: MagnifyingGlass, + skills: [ + { + name: "gmail-get-inbox", + title: t("agent.skill.gmail.skills.getInbox.title"), + description: t("agent.skill.gmail.skills.getInbox.description"), + }, + { + name: "gmail-search", + title: t("agent.skill.gmail.skills.search.title"), + description: t("agent.skill.gmail.skills.search.description"), + }, + { + name: "gmail-read-thread", + title: t("agent.skill.gmail.skills.readThread.title"), + description: t("agent.skill.gmail.skills.readThread.description"), + }, + ], + }, + drafts: { + title: t("agent.skill.gmail.categories.drafts.title"), + description: t("agent.skill.gmail.categories.drafts.description"), + icon: PencilSimple, + skills: [ + { + name: "gmail-create-draft", + title: t("agent.skill.gmail.skills.createDraft.title"), + description: t("agent.skill.gmail.skills.createDraft.description"), + }, + { + name: "gmail-create-draft-reply", + title: t("agent.skill.gmail.skills.createDraftReply.title"), + description: t("agent.skill.gmail.skills.createDraftReply.description"), + }, + { + name: "gmail-update-draft", + title: t("agent.skill.gmail.skills.updateDraft.title"), + description: t("agent.skill.gmail.skills.updateDraft.description"), + }, + { + name: "gmail-get-draft", + title: t("agent.skill.gmail.skills.getDraft.title"), + description: t("agent.skill.gmail.skills.getDraft.description"), + }, + { + name: "gmail-list-drafts", + title: t("agent.skill.gmail.skills.listDrafts.title"), + description: t("agent.skill.gmail.skills.listDrafts.description"), + }, + { + name: "gmail-delete-draft", + title: t("agent.skill.gmail.skills.deleteDraft.title"), + description: t("agent.skill.gmail.skills.deleteDraft.description"), + }, + { + name: "gmail-send-draft", + title: t("agent.skill.gmail.skills.sendDraft.title"), + description: t("agent.skill.gmail.skills.sendDraft.description"), + }, + ], + }, + send: { + title: t("agent.skill.gmail.categories.send.title"), + description: t("agent.skill.gmail.categories.send.description"), + icon: PaperPlaneTilt, + skills: [ + { + name: "gmail-send-email", + title: t("agent.skill.gmail.skills.sendEmail.title"), + description: t("agent.skill.gmail.skills.sendEmail.description"), + }, + { + name: "gmail-reply-to-thread", + title: t("agent.skill.gmail.skills.replyToThread.title"), + description: t("agent.skill.gmail.skills.replyToThread.description"), + }, + ], + }, + threads: { + title: t("agent.skill.gmail.categories.threads.title"), + description: t("agent.skill.gmail.categories.threads.description"), + icon: EnvelopeOpen, + skills: [ + { + name: "gmail-mark-read", + title: t("agent.skill.gmail.skills.markRead.title"), + description: t("agent.skill.gmail.skills.markRead.description"), + }, + { + name: "gmail-mark-unread", + title: t("agent.skill.gmail.skills.markUnread.title"), + description: t("agent.skill.gmail.skills.markUnread.description"), + }, + { + name: "gmail-move-to-trash", + title: t("agent.skill.gmail.skills.moveToTrash.title"), + description: t("agent.skill.gmail.skills.moveToTrash.description"), + }, + { + name: "gmail-move-to-archive", + title: t("agent.skill.gmail.skills.moveToArchive.title"), + description: t("agent.skill.gmail.skills.moveToArchive.description"), + }, + { + name: "gmail-move-to-inbox", + title: t("agent.skill.gmail.skills.moveToInbox.title"), + description: t("agent.skill.gmail.skills.moveToInbox.description"), + }, + ], + }, + account: { + title: t("agent.skill.gmail.categories.account.title"), + description: t("agent.skill.gmail.categories.account.description"), + icon: ChartBar, + skills: [ + { + name: "gmail-get-mailbox-stats", + title: t("agent.skill.gmail.skills.getMailboxStats.title"), + description: t("agent.skill.gmail.skills.getMailboxStats.description"), + }, + ], + }, +}); + +function skillMatchesSearch(skill, searchTerm) { + if (!searchTerm) return true; + + const normalizedSearch = searchTerm.toLowerCase().trim(); + const titleLower = skill.title.toLowerCase(); + const descLower = skill.description.toLowerCase(); + + if (titleLower.includes(normalizedSearch)) return true; + if (descLower.includes(normalizedSearch)) return true; + if (strDistance(titleLower, normalizedSearch) <= LEVENSHTEIN_THRESHOLD) + return true; + + return false; +} + +export function filterSkillCategories(skillCategories, searchTerm) { + if (!searchTerm) return skillCategories; + + const filtered = {}; + for (const [key, category] of Object.entries(skillCategories)) { + const matchingSkills = category.skills.filter((skill) => + skillMatchesSearch(skill, searchTerm) + ); + if (matchingSkills.length > 0) { + filtered[key] = { ...category, skills: matchingSkills }; + } + } + return filtered; +} diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx index 26564cdc..0e106e5a 100644 --- a/frontend/src/pages/Admin/Agents/index.jsx +++ b/frontend/src/pages/Admin/Agents/index.jsx @@ -64,11 +64,26 @@ export default function AdminAgents() { useState(false); const defaultSkills = getDefaultSkills(t); - const configurableSkills = getConfigurableSkills(t, { + const allConfigurableSkills = getConfigurableSkills(t, { fileSystemAgentAvailable, createFilesAgentAvailable, }); + // Filter skills based on mode restrictions + // singleUserOnly -> hidden in multi-user mode + // multiUserOnly -> hidden when NOT in multi-user mode + const isMultiUserMode = settings?.MultiUserMode ?? false; + const configurableSkills = Object.fromEntries( + Object.entries(allConfigurableSkills).filter(([_, skillConfig]) => { + if (!skillConfig.mode) return true; + if (skillConfig.mode.includes("singleUserOnly") && isMultiUserMode) + return false; + if (skillConfig.mode.includes("multiUserOnly") && !isMultiUserMode) + return false; + return true; + }) + ); + // Alert user if they try to leave the page with unsaved changes useEffect(() => { const handleBeforeUnload = (event) => { diff --git a/frontend/src/pages/Admin/Agents/skills.js b/frontend/src/pages/Admin/Agents/skills.js index 8c0aabed..37be377c 100644 --- a/frontend/src/pages/Admin/Agents/skills.js +++ b/frontend/src/pages/Admin/Agents/skills.js @@ -4,6 +4,7 @@ import GenericSkillPanel from "./GenericSkillPanel"; import DefaultSkillPanel from "./DefaultSkillPanel"; import FileSystemSkillPanel from "./FileSystemSkillPanel"; import CreateFileSkillPanel from "./CreateFileSkillPanel"; +import GMailSkillPanel from "./GMailSkillPanel"; import { Brain, File, @@ -11,6 +12,7 @@ import { ChartBar, FolderOpen, FilePlus, + EnvelopeSimple, } from "@phosphor-icons/react"; import RAGImage from "@/media/agents/rag-memory.png"; import SummarizeImage from "@/media/agents/view-summarize.png"; @@ -90,4 +92,12 @@ export const getConfigurableSkills = ( component: AgentSQLConnectorSelection, skill: "sql-agent", }, + "gmail-agent": { + title: t("agent.skill.gmail.title"), + description: t("agent.skill.gmail.description"), + component: GMailSkillPanel, + skill: "gmail-agent", + icon: EnvelopeSimple, + mode: ["singleUserOnly"], + }, }); diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index a9b32df0..b0cb7dc8 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -409,6 +409,15 @@ function adminEndpoints(app) { case "disabled_create_files_skills": requestedSettings[label] = safeJsonParse(setting?.value, []); break; + case "disabled_gmail_skills": + requestedSettings[label] = safeJsonParse(setting?.value, []); + break; + case "gmail_deployment_id": + requestedSettings[label] = setting?.value || null; + break; + case "gmail_api_key": + requestedSettings[label] = setting?.value || null; + break; case "imported_agent_skills": requestedSettings[label] = ImportedPlugin.listImportedPlugins(); break; diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 13ccb5cd..36ca930a 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -35,6 +35,9 @@ const SystemSettings = { "disabled_agent_skills", "disabled_filesystem_skills", "disabled_create_files_skills", + "disabled_gmail_skills", + "gmail_deployment_id", + "gmail_api_key", "imported_agent_skills", "custom_app_name", "feature_flags", @@ -54,6 +57,9 @@ const SystemSettings = { "disabled_agent_skills", "disabled_filesystem_skills", "disabled_create_files_skills", + "disabled_gmail_skills", + "gmail_deployment_id", + "gmail_api_key", "agent_sql_connections", "custom_app_name", "default_system_prompt", @@ -174,6 +180,33 @@ const SystemSettings = { return JSON.stringify([]); } }, + disabled_gmail_skills: (updates) => { + try { + const skills = updates.split(",").filter((skill) => !!skill); + return JSON.stringify(skills); + } catch { + console.error(`Could not validate disabled gmail skills.`); + return JSON.stringify([]); + } + }, + gmail_deployment_id: (update) => { + try { + if (!update || typeof update !== "string") return null; + return String(update).trim(); + } finally { + const GmailBridge = require("../utils/agents/aibitat/plugins/gmail/lib"); + GmailBridge.reset(); + } + }, + gmail_api_key: (update) => { + try { + if (!update || typeof update !== "string") return null; + return String(update).trim(); + } finally { + const GmailBridge = require("../utils/agents/aibitat/plugins/gmail/lib"); + GmailBridge.reset(); + } + }, agent_sql_connections: async (updates) => { const existingConnections = safeJsonParse( (await SystemSettings.get({ label: "agent_sql_connections" }))?.value, diff --git a/server/utils/agents/aibitat/plugins/gmail/account/gmail-get-mailbox-stats.js b/server/utils/agents/aibitat/plugins/gmail/account/gmail-get-mailbox-stats.js new file mode 100644 index 00000000..c63b6262 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/account/gmail-get-mailbox-stats.js @@ -0,0 +1,75 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailGetMailboxStats = { + name: "gmail-get-mailbox-stats", + plugin: function () { + return { + name: "gmail-get-mailbox-stats", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Used for general account information. Reports Gmail mailbox statistics including unread counts for inbox, " + + "priority inbox, starred messages, and spam folder.", + examples: [ + { + prompt: "How much of my mailbox quota is remaining?", + call: JSON.stringify({}), + }, + { + prompt: "Show me my mailbox statistics", + call: JSON.stringify({}), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: {}, + additionalProperties: false, + }, + handler: async function () { + try { + this.super.handlerProps.log( + `Using the gmail-get-mailbox-stats tool.` + ); + + this.super.introspect( + `${this.caller}: Getting Gmail mailbox statistics` + ); + + const result = await gmailLib.getMailboxStats(); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to get mailbox stats - ${result.error}` + ); + return `Error getting mailbox statistics: ${result.error}`; + } + + const stats = result.data; + this.super.introspect( + `${this.caller}: Successfully retrieved mailbox statistics` + ); + + return ( + `Gmail Mailbox Statistics:\n\n` + + `Inbox Unread: ${stats.inboxUnreadCount}\n` + + `Priority Inbox Unread: ${stats.priorityInboxUnreadCount}\n` + + `Starred Unread: ${stats.starredUnreadCount}\n` + + `Spam Unread: ${stats.spamUnreadCount}\n\n` + + `Use gmail-search to find and read specific emails.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-get-mailbox-stats error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error getting mailbox statistics: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft-reply.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft-reply.js new file mode 100644 index 00000000..1809d515 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft-reply.js @@ -0,0 +1,220 @@ +const gmailLib = require("../lib.js"); +const { prepareAttachment, MAX_TOTAL_ATTACHMENT_SIZE } = require("../lib.js"); +const { humanFileSize } = require("../../../../../helpers"); + +module.exports.GmailCreateDraftReply = { + name: "gmail-create-draft-reply", + plugin: function () { + return { + name: "gmail-create-draft-reply", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Create a draft reply to an existing email thread in Gmail. " + + "The draft will be saved but not sent. You can choose to reply to all recipients or just the sender. " + + "Supports file attachments.", + examples: [ + { + prompt: "Create a draft reply to thread 18abc123def", + call: JSON.stringify({ + threadId: "18abc123def", + body: "Thank you for your email. I will review this and get back to you shortly.", + replyAll: false, + }), + }, + { + prompt: "Draft a reply-all response to the thread", + call: JSON.stringify({ + threadId: "18abc123def", + body: "Thanks everyone for your input. Here are my thoughts...", + replyAll: true, + }), + }, + { + prompt: "Create a draft reply with an attachment", + call: JSON.stringify({ + threadId: "18abc123def", + body: "Please find the requested document attached.", + attachments: ["/Users/me/Documents/document.pdf"], + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to reply to.", + }, + body: { + type: "string", + description: "Plain text reply body content.", + }, + replyAll: { + type: "boolean", + description: + "Whether to reply to all recipients. Defaults to false (reply to sender only).", + default: false, + }, + cc: { + type: "string", + description: + "Additional CC recipient email address(es). Optional.", + }, + bcc: { + type: "string", + description: "BCC recipient email address(es). Optional.", + }, + htmlBody: { + type: "string", + description: "HTML version of the reply body. Optional.", + }, + attachments: { + type: "array", + items: { type: "string" }, + description: + "Array of absolute file paths to attach to the draft reply.", + }, + }, + required: ["threadId", "body"], + additionalProperties: false, + }, + handler: async function ({ + threadId, + body, + replyAll = false, + cc, + bcc, + htmlBody, + attachments, + }) { + try { + this.super.handlerProps.log( + `Using the gmail-create-draft-reply tool.` + ); + + if (!threadId || !body) { + return "Error: 'threadId' and 'body' are required."; + } + + const preparedAttachments = []; + const attachmentSummaries = []; + let totalAttachmentSize = 0; + + if (Array.isArray(attachments) && attachments.length > 0) { + this.super.introspect( + `${this.caller}: Validating ${attachments.length} attachment(s)...` + ); + + for (const filePath of attachments) { + const result = prepareAttachment(filePath); + if (!result.success) { + this.super.introspect( + `${this.caller}: Attachment validation failed - ${result.error}` + ); + return `Error with attachment: ${result.error}`; + } + + totalAttachmentSize += result.fileInfo.size; + if (totalAttachmentSize > MAX_TOTAL_ATTACHMENT_SIZE) { + const totalFormatted = humanFileSize( + totalAttachmentSize, + true + ); + this.super.introspect( + `${this.caller}: Total attachment size (${totalFormatted}) exceeds 20MB limit` + ); + return `Error: Total attachment size (${totalFormatted}) exceeds the 20MB limit. Please reduce the number or size of attachments.`; + } + + preparedAttachments.push(result.attachment); + attachmentSummaries.push( + `${result.fileInfo.name} (${result.fileInfo.sizeFormatted})` + ); + this.super.introspect( + `${this.caller}: Prepared attachment "${result.fileInfo.name}"` + ); + } + } + + if (this.super.requestToolApproval) { + const attachmentNote = + preparedAttachments.length > 0 + ? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}` + : ""; + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + threadId, + replyAll, + attachmentCount: preparedAttachments.length, + }, + description: `Create Gmail draft reply to thread "${threadId}"${replyAll ? " (reply all)" : ""}${attachmentNote}`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Creating draft reply to thread ${threadId}${replyAll ? " (reply all)" : ""}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}` + ); + + const options = {}; + if (cc) options.cc = cc; + if (bcc) options.bcc = bcc; + if (htmlBody) options.htmlBody = htmlBody; + if (preparedAttachments.length > 0) { + options.attachments = preparedAttachments; + } + + const result = await gmailLib.createDraftReply( + threadId, + body, + replyAll, + options + ); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to create draft reply - ${result.error}` + ); + return `Error creating Gmail draft reply: ${result.error}`; + } + + const draft = result.data; + this.super.introspect( + `${this.caller}: Successfully created draft reply (ID: ${draft.draftId})` + ); + + return ( + `Successfully created Gmail draft reply:\n` + + `Draft ID: ${draft.draftId}\n` + + `Message ID: ${draft.messageId}\n` + + `To: ${draft.to}\n` + + `Subject: ${draft.subject}\n` + + `Reply Type: ${replyAll ? "Reply All" : "Reply"}\n` + + (preparedAttachments.length > 0 + ? `Attachments: ${attachmentSummaries.join(", ")}\n` + : "") + + `\nThe draft reply has been saved and can be edited or sent later.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-create-draft-reply error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error creating Gmail draft reply: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft.js new file mode 100644 index 00000000..9b2b0ee2 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-create-draft.js @@ -0,0 +1,217 @@ +const gmailLib = require("../lib.js"); +const { prepareAttachment, MAX_TOTAL_ATTACHMENT_SIZE } = require("../lib.js"); +const { humanFileSize } = require("../../../../../helpers"); + +module.exports.GmailCreateDraft = { + name: "gmail-create-draft", + plugin: function () { + return { + name: "gmail-create-draft", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Create a new draft email in Gmail. The draft will be saved but not sent. " + + "You can optionally include CC, BCC recipients, HTML body content, and file attachments.", + examples: [ + { + prompt: + "Create a draft email to john@example.com about the meeting", + call: JSON.stringify({ + to: "john@example.com", + subject: "Meeting Tomorrow", + body: "Hi John,\n\nJust wanted to confirm our meeting tomorrow at 2pm.\n\nBest regards", + }), + }, + { + prompt: "Draft an email with CC recipients", + call: JSON.stringify({ + to: "john@example.com", + subject: "Project Update", + body: "Please see the attached project update.", + cc: "manager@example.com", + }), + }, + { + prompt: "Create a draft with an attachment", + call: JSON.stringify({ + to: "john@example.com", + subject: "Report", + body: "Please find the report attached.", + attachments: ["/Users/me/Documents/report.pdf"], + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + to: { + type: "string", + description: + "Recipient email address(es). Multiple addresses can be comma-separated.", + }, + subject: { + type: "string", + description: "Email subject line.", + }, + body: { + type: "string", + description: "Plain text email body content.", + }, + cc: { + type: "string", + description: "CC recipient email address(es). Optional.", + }, + bcc: { + type: "string", + description: "BCC recipient email address(es). Optional.", + }, + htmlBody: { + type: "string", + description: "HTML version of the email body. Optional.", + }, + attachments: { + type: "array", + items: { type: "string" }, + description: + "Array of absolute file paths to attach to the draft.", + }, + }, + required: ["to", "subject", "body"], + additionalProperties: false, + }, + handler: async function ({ + to, + subject, + body, + cc, + bcc, + htmlBody, + attachments, + }) { + try { + this.super.handlerProps.log(`Using the gmail-create-draft tool.`); + + if (!to || !subject || !body) { + return "Error: 'to', 'subject', and 'body' are required."; + } + + const preparedAttachments = []; + const attachmentSummaries = []; + let totalAttachmentSize = 0; + + if (Array.isArray(attachments) && attachments.length > 0) { + this.super.introspect( + `${this.caller}: Validating ${attachments.length} attachment(s)...` + ); + + for (const filePath of attachments) { + const result = prepareAttachment(filePath); + if (!result.success) { + this.super.introspect( + `${this.caller}: Attachment validation failed - ${result.error}` + ); + return `Error with attachment: ${result.error}`; + } + + totalAttachmentSize += result.fileInfo.size; + if (totalAttachmentSize > MAX_TOTAL_ATTACHMENT_SIZE) { + const totalFormatted = humanFileSize( + totalAttachmentSize, + true + ); + this.super.introspect( + `${this.caller}: Total attachment size (${totalFormatted}) exceeds 20MB limit` + ); + return `Error: Total attachment size (${totalFormatted}) exceeds the 20MB limit. Please reduce the number or size of attachments.`; + } + + preparedAttachments.push(result.attachment); + attachmentSummaries.push( + `${result.fileInfo.name} (${result.fileInfo.sizeFormatted})` + ); + this.super.introspect( + `${this.caller}: Prepared attachment "${result.fileInfo.name}"` + ); + } + } + + if (this.super.requestToolApproval) { + const attachmentNote = + preparedAttachments.length > 0 + ? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}` + : ""; + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + to, + subject, + attachmentCount: preparedAttachments.length, + }, + description: `Create Gmail draft to "${to}" with subject "${subject}"${attachmentNote}`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Creating Gmail draft to ${to}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}` + ); + + const options = {}; + if (cc) options.cc = cc; + if (bcc) options.bcc = bcc; + if (htmlBody) options.htmlBody = htmlBody; + if (preparedAttachments.length > 0) { + options.attachments = preparedAttachments; + } + + const result = await gmailLib.createDraft( + to, + subject, + body, + options + ); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to create draft - ${result.error}` + ); + return `Error creating Gmail draft: ${result.error}`; + } + + const draft = result.data; + this.super.introspect( + `${this.caller}: Successfully created draft (ID: ${draft.draftId})` + ); + + return ( + `Successfully created Gmail draft:\n` + + `Draft ID: ${draft.draftId}\n` + + `Message ID: ${draft.messageId}\n` + + `To: ${draft.to}\n` + + `Subject: ${draft.subject}\n` + + (preparedAttachments.length > 0 + ? `Attachments: ${attachmentSummaries.join(", ")}\n` + : "") + + `\nThe draft has been saved and can be edited or sent later.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-create-draft error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error creating Gmail draft: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-delete-draft.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-delete-draft.js new file mode 100644 index 00000000..ce569e8e --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-delete-draft.js @@ -0,0 +1,87 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailDeleteDraft = { + name: "gmail-delete-draft", + plugin: function () { + return { + name: "gmail-delete-draft", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Delete a draft email from Gmail. " + + "This action is permanent and cannot be undone.", + examples: [ + { + prompt: "Delete the draft with ID r123456", + call: JSON.stringify({ + draftId: "r123456", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + draftId: { + type: "string", + description: "The Gmail draft ID to delete.", + }, + }, + required: ["draftId"], + additionalProperties: false, + }, + handler: async function ({ draftId }) { + try { + this.super.handlerProps.log(`Using the gmail-delete-draft tool.`); + + if (!draftId) { + return "Error: 'draftId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { draftId }, + description: `Delete Gmail draft "${draftId}"`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Deleting Gmail draft ${draftId}` + ); + + const result = await gmailLib.deleteDraft(draftId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to delete draft - ${result.error}` + ); + return `Error deleting Gmail draft: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully deleted draft ${draftId}` + ); + + return `Successfully deleted Gmail draft (ID: ${draftId}).`; + } catch (e) { + this.super.handlerProps.log( + `gmail-delete-draft error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error deleting Gmail draft: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-get-draft.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-get-draft.js new file mode 100644 index 00000000..66c3829c --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-get-draft.js @@ -0,0 +1,84 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailGetDraft = { + name: "gmail-get-draft", + plugin: function () { + return { + name: "gmail-get-draft", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Retrieve a specific draft email by its ID. " + + "Returns the draft details including recipient, subject, and body content.", + examples: [ + { + prompt: "Get the draft with ID r123456", + call: JSON.stringify({ + draftId: "r123456", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + draftId: { + type: "string", + description: "The Gmail draft ID to retrieve.", + }, + }, + required: ["draftId"], + additionalProperties: false, + }, + handler: async function ({ draftId }) { + try { + this.super.handlerProps.log(`Using the gmail-get-draft tool.`); + + if (!draftId) { + return "Error: 'draftId' is required."; + } + + this.super.introspect( + `${this.caller}: Retrieving Gmail draft ${draftId}` + ); + + const result = await gmailLib.getDraft(draftId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to get draft - ${result.error}` + ); + return `Error retrieving Gmail draft: ${result.error}`; + } + + const draft = result.data; + this.super.introspect( + `${this.caller}: Successfully retrieved draft (ID: ${draft.draftId})` + ); + + return ( + `Gmail Draft:\n` + + `Draft ID: ${draft.draftId}\n` + + `Message ID: ${draft.messageId}\n` + + `To: ${draft.to}\n` + + (draft.cc ? `CC: ${draft.cc}\n` : "") + + (draft.bcc ? `BCC: ${draft.bcc}\n` : "") + + `Subject: ${draft.subject}\n` + + `Date: ${new Date(draft.date).toLocaleString()}\n` + + `\n--- Body ---\n${draft.body}` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-get-draft error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error retrieving Gmail draft: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-list-drafts.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-list-drafts.js new file mode 100644 index 00000000..e41241d7 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-list-drafts.js @@ -0,0 +1,96 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailListDrafts = { + name: "gmail-list-drafts", + plugin: function () { + return { + name: "gmail-list-drafts", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "List all draft emails in Gmail. " + + "Returns a summary of each draft including ID, recipient, subject, and date.", + examples: [ + { + prompt: "List my email drafts", + call: JSON.stringify({ + limit: 25, + }), + }, + { + prompt: "Show me the first 10 drafts", + call: JSON.stringify({ + limit: 10, + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + limit: { + type: "number", + description: + "Maximum number of drafts to return (1-100). Defaults to 25.", + default: 25, + }, + }, + additionalProperties: false, + }, + handler: async function ({ limit = 25 }) { + try { + this.super.handlerProps.log(`Using the gmail-list-drafts tool.`); + + this.super.introspect( + `${this.caller}: Listing Gmail drafts (limit: ${limit})` + ); + + const result = await gmailLib.listDrafts(limit); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to list drafts - ${result.error}` + ); + return `Error listing Gmail drafts: ${result.error}`; + } + + const { totalDrafts, returned, drafts } = result.data; + this.super.introspect( + `${this.caller}: Found ${totalDrafts} total drafts, returning ${returned}` + ); + + if (totalDrafts === 0) { + return "No drafts found in Gmail."; + } + + const summary = drafts + .map( + (d, i) => + `${i + 1}. Draft ID: ${d.draftId}\n` + + ` To: ${d.to || "(no recipient)"}\n` + + ` Subject: ${d.subject || "(no subject)"}\n` + + ` Date: ${new Date(d.date).toLocaleString()}` + ) + .join("\n\n"); + + return ( + `Gmail Drafts (${returned} of ${totalDrafts} total):\n\n${summary}\n\n` + + `Use the draft ID with gmail-get-draft to view full content, ` + + `gmail-update-draft to edit, gmail-delete-draft to remove, ` + + `or gmail-send-draft to send.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-list-drafts error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error listing Gmail drafts: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-send-draft.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-send-draft.js new file mode 100644 index 00000000..e06bc6ba --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-send-draft.js @@ -0,0 +1,94 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailSendDraft = { + name: "gmail-send-draft", + plugin: function () { + return { + name: "gmail-send-draft", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Send an existing draft email from Gmail. " + + "This will send the draft immediately and remove it from drafts. " + + "This action cannot be undone.", + examples: [ + { + prompt: "Send the draft with ID r123456", + call: JSON.stringify({ + draftId: "r123456", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + draftId: { + type: "string", + description: "The Gmail draft ID to send.", + }, + }, + required: ["draftId"], + additionalProperties: false, + }, + handler: async function ({ draftId }) { + try { + this.super.handlerProps.log(`Using the gmail-send-draft tool.`); + + if (!draftId) { + return "Error: 'draftId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { draftId }, + description: `Send Gmail draft "${draftId}" - This will send the email immediately`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Sending Gmail draft ${draftId}` + ); + + const result = await gmailLib.sendDraft(draftId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to send draft - ${result.error}` + ); + return `Error sending Gmail draft: ${result.error}`; + } + + const { messageId, threadId } = result.data; + this.super.introspect( + `${this.caller}: Successfully sent draft as message ${messageId}` + ); + + return ( + `Successfully sent Gmail draft:\n` + + `Message ID: ${messageId}\n` + + `Thread ID: ${threadId}\n\n` + + `The email has been sent and removed from drafts.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-send-draft error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error sending Gmail draft: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-update-draft.js b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-update-draft.js new file mode 100644 index 00000000..8f7ae006 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/drafts/gmail-update-draft.js @@ -0,0 +1,217 @@ +const gmailLib = require("../lib.js"); +const { prepareAttachment, MAX_TOTAL_ATTACHMENT_SIZE } = require("../lib.js"); +const { humanFileSize } = require("../../../../../helpers"); + +module.exports.GmailUpdateDraft = { + name: "gmail-update-draft", + plugin: function () { + return { + name: "gmail-update-draft", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Update an existing draft email in Gmail. " + + "You must provide the draft ID and the new content for the draft. " + + "Supports file attachments.", + examples: [ + { + prompt: "Update draft r123456 with new content", + call: JSON.stringify({ + draftId: "r123456", + to: "john@example.com", + subject: "Updated: Meeting Tomorrow", + body: "Hi John,\n\nThe meeting has been rescheduled to 3pm.\n\nBest regards", + }), + }, + { + prompt: "Update draft with an attachment", + call: JSON.stringify({ + draftId: "r123456", + to: "john@example.com", + subject: "Report", + body: "Please find the updated report attached.", + attachments: ["/Users/me/Documents/report.pdf"], + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + draftId: { + type: "string", + description: "The Gmail draft ID to update.", + }, + to: { + type: "string", + description: + "Recipient email address(es). Multiple addresses can be comma-separated.", + }, + subject: { + type: "string", + description: "Email subject line.", + }, + body: { + type: "string", + description: "Plain text email body content.", + }, + cc: { + type: "string", + description: "CC recipient email address(es). Optional.", + }, + bcc: { + type: "string", + description: "BCC recipient email address(es). Optional.", + }, + htmlBody: { + type: "string", + description: "HTML version of the email body. Optional.", + }, + attachments: { + type: "array", + items: { type: "string" }, + description: + "Array of absolute file paths to attach to the draft.", + }, + }, + required: ["draftId", "to", "subject", "body"], + additionalProperties: false, + }, + handler: async function ({ + draftId, + to, + subject, + body, + cc, + bcc, + htmlBody, + attachments, + }) { + try { + this.super.handlerProps.log(`Using the gmail-update-draft tool.`); + + if (!draftId || !to || !subject) { + return "Error: 'draftId', 'to', and 'subject' are required."; + } + + const preparedAttachments = []; + const attachmentSummaries = []; + let totalAttachmentSize = 0; + + if (Array.isArray(attachments) && attachments.length > 0) { + this.super.introspect( + `${this.caller}: Validating ${attachments.length} attachment(s)...` + ); + + for (const filePath of attachments) { + const result = prepareAttachment(filePath); + if (!result.success) { + this.super.introspect( + `${this.caller}: Attachment validation failed - ${result.error}` + ); + return `Error with attachment: ${result.error}`; + } + + totalAttachmentSize += result.fileInfo.size; + if (totalAttachmentSize > MAX_TOTAL_ATTACHMENT_SIZE) { + const totalFormatted = humanFileSize( + totalAttachmentSize, + true + ); + this.super.introspect( + `${this.caller}: Total attachment size (${totalFormatted}) exceeds 20MB limit` + ); + return `Error: Total attachment size (${totalFormatted}) exceeds the 20MB limit. Please reduce the number or size of attachments.`; + } + + preparedAttachments.push(result.attachment); + attachmentSummaries.push( + `${result.fileInfo.name} (${result.fileInfo.sizeFormatted})` + ); + this.super.introspect( + `${this.caller}: Prepared attachment "${result.fileInfo.name}"` + ); + } + } + + if (this.super.requestToolApproval) { + const attachmentNote = + preparedAttachments.length > 0 + ? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}` + : ""; + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + draftId, + to, + subject, + attachmentCount: preparedAttachments.length, + }, + description: `Update Gmail draft "${draftId}"${attachmentNote}`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Updating Gmail draft ${draftId}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}` + ); + + const options = {}; + if (cc) options.cc = cc; + if (bcc) options.bcc = bcc; + if (htmlBody) options.htmlBody = htmlBody; + if (preparedAttachments.length > 0) { + options.attachments = preparedAttachments; + } + + const result = await gmailLib.updateDraft( + draftId, + to, + subject, + body, + options + ); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to update draft - ${result.error}` + ); + return `Error updating Gmail draft: ${result.error}`; + } + + const draft = result.data; + this.super.introspect( + `${this.caller}: Successfully updated draft (ID: ${draft.draftId})` + ); + + return ( + `Successfully updated Gmail draft:\n` + + `Draft ID: ${draft.draftId}\n` + + `Message ID: ${draft.messageId}\n` + + `To: ${draft.to}\n` + + `Subject: ${draft.subject}\n` + + (preparedAttachments.length > 0 + ? `Attachments: ${attachmentSummaries.join(", ")}\n` + : "") + + `\nThe draft has been updated.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-update-draft error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error updating Gmail draft: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/index.js b/server/utils/agents/aibitat/plugins/gmail/index.js new file mode 100644 index 00000000..506a9ed7 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/index.js @@ -0,0 +1,73 @@ +// Get Inbox & Search +const { GmailGetInbox } = require("./search/gmail-get-inbox.js"); +const { GmailSearch } = require("./search/gmail-search.js"); +const { GmailReadThread } = require("./search/gmail-read-thread.js"); + +// Drafts +const { GmailCreateDraft } = require("./drafts/gmail-create-draft.js"); +const { + GmailCreateDraftReply, +} = require("./drafts/gmail-create-draft-reply.js"); +const { GmailUpdateDraft } = require("./drafts/gmail-update-draft.js"); +const { GmailGetDraft } = require("./drafts/gmail-get-draft.js"); +const { GmailListDrafts } = require("./drafts/gmail-list-drafts.js"); +const { GmailDeleteDraft } = require("./drafts/gmail-delete-draft.js"); +const { GmailSendDraft } = require("./drafts/gmail-send-draft.js"); + +// Send & Reply +const { GmailSendEmail } = require("./send/gmail-send-email.js"); +const { GmailReplyToThread } = require("./send/gmail-reply-to-thread.js"); + +// Thread Management +const { GmailMarkRead } = require("./threads/gmail-mark-read.js"); +const { GmailMarkUnread } = require("./threads/gmail-mark-unread.js"); +const { GmailMoveToTrash } = require("./threads/gmail-move-to-trash.js"); +const { GmailMoveToArchive } = require("./threads/gmail-move-to-archive.js"); +const { GmailMoveToInbox } = require("./threads/gmail-move-to-inbox.js"); + +// Account +const { + GmailGetMailboxStats, +} = require("./account/gmail-get-mailbox-stats.js"); + +const gmailAgent = { + name: "gmail-agent", + startupConfig: { + params: {}, + }, + plugin: [ + // Alias for easy access to the inbox + GmailGetInbox, + + // Search & Read (read-only) + GmailSearch, + GmailReadThread, + + // Drafts (modifying) + GmailCreateDraft, + GmailCreateDraftReply, + GmailUpdateDraft, + GmailGetDraft, + GmailListDrafts, + GmailDeleteDraft, + GmailSendDraft, + + // Send & Reply (modifying) + GmailSendEmail, + GmailReplyToThread, + + // Thread Management (modifying) + GmailMarkRead, + GmailMarkUnread, + GmailMoveToTrash, + GmailMoveToArchive, + GmailMoveToInbox, + + // Account (read-only) + GmailGetMailboxStats, + ], +}; + +module.exports = { + gmailAgent, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/lib.js b/server/utils/agents/aibitat/plugins/gmail/lib.js new file mode 100644 index 00000000..711e450e --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/lib.js @@ -0,0 +1,555 @@ +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const mime = require("mime"); +const { SystemSettings } = require("../../../../../models/systemSettings"); +const { CollectorApi } = require("../../../../collectorApi"); +const { humanFileSize } = require("../../../../helpers"); + +const MAX_TOTAL_ATTACHMENT_SIZE = 20 * 1024 * 1024; // 20MB limit for all attachments combined + +/** + * Validates and prepares a file attachment for email. + * Note: Does not check total size limit - caller should track cumulative size. + * @param {string} filePath - Absolute path to the file + * @returns {{success: boolean, attachment?: object, error?: string, fileInfo?: object}} + */ +function prepareAttachment(filePath) { + if (process.env.ANYTHING_LLM_RUNTIME === "docker") { + return { + success: false, + error: "File attachments are not supported in Docker environments.", + }; + } + + if (!path.isAbsolute(filePath)) { + return { success: false, error: `Path must be absolute: ${filePath}` }; + } + + if (!fs.existsSync(filePath)) { + return { success: false, error: `File does not exist: ${filePath}` }; + } + + const stats = fs.statSync(filePath); + if (!stats.isFile()) { + return { success: false, error: `Path is not a file: ${filePath}` }; + } + + if (stats.size === 0) { + return { success: false, error: `File is empty: ${filePath}` }; + } + + try { + const fileBuffer = fs.readFileSync(filePath); + const base64Data = fileBuffer.toString("base64"); + const fileName = path.basename(filePath); + const contentType = mime.getType(filePath) || "application/octet-stream"; + + return { + success: true, + attachment: { + name: fileName, + contentType, + data: base64Data, + }, + fileInfo: { + path: filePath, + name: fileName, + size: stats.size, + sizeFormatted: humanFileSize(stats.size, true), + contentType, + }, + }; + } catch (e) { + return { success: false, error: `Failed to read file: ${e.message}` }; + } +} + +/** + * Parse an attachment using the CollectorApi for secure content extraction. + * Writes the base64 data to a temp file, parses it, then cleans up. + * @param {Object} attachment - The attachment object with name, contentType, size, data (base64) + * @returns {Promise<{success: boolean, content: string|null, error: string|null}>} + */ +async function parseAttachment(attachment) { + const tempDir = os.tmpdir(); + const safeFilename = attachment.name.replace(/[^a-zA-Z0-9._-]/g, "_"); + const tempFilePath = path.join( + tempDir, + `gmail-attachment-${Date.now()}-${safeFilename}` + ); + + try { + const buffer = Buffer.from(attachment.data, "base64"); + fs.writeFileSync(tempFilePath, buffer); + + const collector = new CollectorApi(); + const result = await collector.parseDocument(safeFilename, { + absolutePath: tempFilePath, + }); + + if (fs.existsSync(tempFilePath)) { + fs.unlinkSync(tempFilePath); + } + + if (!result.success) { + return { + success: false, + content: null, + error: result.reason || "Failed to parse attachment", + }; + } + + const textContent = result.documents + ?.map((doc) => doc.pageContent || doc.content || "") + .filter(Boolean) + .join("\n\n"); + + return { + success: true, + content: textContent || "(No text content extracted)", + error: null, + }; + } catch (e) { + if (fs.existsSync(tempFilePath)) { + try { + fs.unlinkSync(tempFilePath); + } catch {} + } + return { success: false, content: null, error: e.message }; + } +} + +/** + * Collect attachments from messages and optionally parse them with user approval. + * @param {Object} context - The handler context (this) from the aibitat function + * @param {Array} messages - Array of message objects (single message should be wrapped in array) + * @returns {Promise<{allAttachments: Array, parsedContent: string}>} + */ +async function handleAttachments(context, messages) { + const allAttachments = []; + messages.forEach((msg, msgIndex) => { + if (msg.attachments?.length > 0) { + msg.attachments.forEach((att) => { + allAttachments.push({ + ...att, + messageIndex: msgIndex + 1, + messageId: msg.id, + }); + }); + } + }); + + let parsedContent = ""; + const citations = []; + if (allAttachments.length > 0 && context.super.requestToolApproval) { + const attachmentNames = allAttachments.map((a) => a.name).join(", "); + + const approval = await context.super.requestToolApproval({ + skillName: context.name, + payload: { attachments: attachmentNames }, + description: `Parse attachments (${attachmentNames}) to extract text content?`, + }); + + if (approval.approved) { + context.super.introspect( + `${context.caller}: Parsing ${allAttachments.length} attachment(s)...` + ); + + const parsedResults = []; + for (const attachment of allAttachments) { + if (!attachment.data) continue; + context.super.introspect( + `${context.caller}: Parsing "${attachment.name}"...` + ); + const parseResult = await parseAttachment(attachment); + if (!parseResult.success) continue; + + citations.push({ + id: `gmail-attachment-${attachment.messageId}-${attachment.name}`, + title: attachment.name, + text: parseResult.content, + chunkSource: "gmail-attachment://" + attachment.name, + score: null, + }); + parsedResults.push({ + name: attachment.name, + messageIndex: attachment.messageIndex, + ...parseResult, + }); + } + + parsedContent = + "\n\n--- Parsed Attachment Content ---\n" + + parsedResults + .map((r) => `\n[Message ${r.messageIndex}: ${r.name}]\n${r.content}`) + .join("\n"); + + context.super.introspect( + `${context.caller}: Finished parsing attachments` + ); + } else { + context.super.introspect( + `${context.caller}: User declined to parse attachments` + ); + } + } + + citations.forEach((c) => context.super.addCitation?.(c)); + return { allAttachments, parsedContent }; +} + +/** + * Gmail Bridge Library + * Handles communication with the AnythingLLM Gmail Google Apps Script deployment. + */ +class GmailBridge { + #deploymentId = null; + #apiKey = null; + #isInitialized = false; + + #log(text, ...args) { + console.log(`\x1b[36m[GmailBridge]\x1b[0m ${text}`, ...args); + } + + /** + * Resets the bridge state, forcing re-initialization on next use. + * Call this when configuration changes (e.g., deployment ID updated). + */ + reset() { + this.#deploymentId = null; + this.#apiKey = null; + this.#isInitialized = false; + } + + /** + * Initializes the Gmail bridge by fetching configuration from system settings. + * @returns {Promise<{success: boolean, error?: string}>} + */ + async initialize() { + if (this.#isInitialized) return { success: true }; + + try { + const isMultiUser = await SystemSettings.isMultiUserMode(); + if (isMultiUser) { + return { + success: false, + error: + "Gmail integration is not available in multi-user mode for security reasons.", + }; + } + + const deploymentId = await SystemSettings.getValueOrFallback( + { label: "gmail_deployment_id" }, + null + ); + const apiKey = await SystemSettings.getValueOrFallback( + { label: "gmail_api_key" }, + null + ); + + if (!deploymentId || !apiKey) { + return { + success: false, + error: + "Gmail integration is not configured. Please set the Deployment ID and API Key in the agent settings.", + }; + } + + this.#deploymentId = deploymentId; + this.#apiKey = apiKey; + this.#isInitialized = true; + return { success: true }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + /** + * Checks if the Gmail bridge is properly configured and available. + * @returns {Promise} + */ + async isAvailable() { + const result = await this.initialize(); + return result.success; + } + + /** + * Checks if Gmail tools are available (not in multi-user mode and has configuration). + * @returns {Promise} + */ + static async isToolAvailable() { + const isMultiUser = await SystemSettings.isMultiUserMode(); + if (isMultiUser) return false; + + const deploymentId = await SystemSettings.getValueOrFallback( + { label: "gmail_deployment_id" }, + null + ); + const apiKey = await SystemSettings.getValueOrFallback( + { label: "gmail_api_key" }, + null + ); + + return !!(deploymentId && apiKey); + } + + get maskedDeploymentId() { + if (!this.#deploymentId) return "(not configured)"; + return ( + this.#deploymentId.substring(0, 5) + + "..." + + this.#deploymentId.substring(this.#deploymentId.length - 5) + ); + } + + /** + * Gets the base URL for the Gmail Google Apps Script deployment. + * @returns {string} + */ + #getBaseUrl() { + this.#log(`Getting base URL for deployment ID ${this.maskedDeploymentId}`); + return `https://script.google.com/macros/s/${this.#deploymentId}/exec`; + } + + /** + * Makes a request to the Gmail Google Apps Script API. + * @param {string} action - The action to perform + * @param {object} params - Additional parameters for the action + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async request(action, params = {}) { + const initResult = await this.initialize(); + if (!initResult.success) { + return { success: false, error: initResult.error }; + } + + try { + const response = await fetch(this.#getBaseUrl(), { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-AnythingLLM-UA": "AnythingLLM-Gmail-Agent/1.0", + }, + body: JSON.stringify({ + key: this.#apiKey, + action, + ...params, + }), + }); + + if (!response.ok) { + return { + success: false, + error: `Gmail API request failed with status ${response.status}`, + }; + } + + const result = await response.json(); + + if (result.status === "error") { + return { success: false, error: result.error }; + } + + return { success: true, data: result.data, quota: result.quota }; + } catch (error) { + return { + success: false, + error: `Gmail API request failed: ${error.message}`, + }; + } + } + + /** + * Search emails using Gmail query syntax. + * @param {string} query - Gmail search query + * @param {number} limit - Maximum results to return + * @param {number} start - Starting offset + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async search(query = "is:inbox", limit = 10, start = 0) { + return this.request("search", { query, limit, start }); + } + + /** + * Read a full thread by ID. + * @param {string} threadId - The thread ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async readThread(threadId) { + return this.request("read_thread", { threadId }); + } + + /** + * Create a new draft email. + * @param {string} to - Recipient email + * @param {string} subject - Email subject + * @param {string} body - Email body + * @param {object} options - Additional options (cc, bcc, htmlBody, etc.) + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async createDraft(to, subject, body, options = {}) { + return this.request("create_draft", { to, subject, body, ...options }); + } + + /** + * Create a draft reply to an existing thread. + * @param {string} threadId - The thread ID to reply to + * @param {string} body - Reply body + * @param {boolean} replyAll - Whether to reply all + * @param {object} options - Additional options + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async createDraftReply(threadId, body, replyAll = false, options = {}) { + return this.request("create_draft_reply", { + threadId, + body, + replyAll, + ...options, + }); + } + + /** + * Update an existing draft. + * @param {string} draftId - The draft ID + * @param {string} to - Recipient email + * @param {string} subject - Email subject + * @param {string} body - Email body + * @param {object} options - Additional options + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async updateDraft(draftId, to, subject, body, options = {}) { + return this.request("update_draft", { + draftId, + to, + subject, + body, + ...options, + }); + } + + /** + * Get a specific draft by ID. + * @param {string} draftId - The draft ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async getDraft(draftId) { + return this.request("get_draft", { draftId }); + } + + /** + * List all drafts. + * @param {number} limit - Maximum drafts to return + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async listDrafts(limit = 25) { + return this.request("list_drafts", { limit }); + } + + /** + * Delete a draft. + * @param {string} draftId - The draft ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async deleteDraft(draftId) { + return this.request("delete_draft", { draftId }); + } + + /** + * Send an existing draft. + * @param {string} draftId - The draft ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async sendDraft(draftId) { + return this.request("send_draft", { draftId }); + } + + /** + * Send an email immediately. + * @param {string} to - Recipient email + * @param {string} subject - Email subject + * @param {string} body - Email body + * @param {object} options - Additional options + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async sendEmail(to, subject, body, options = {}) { + return this.request("send_email", { to, subject, body, ...options }); + } + + /** + * Reply to a thread immediately. + * @param {string} threadId - The thread ID + * @param {string} body - Reply body + * @param {boolean} replyAll - Whether to reply all + * @param {object} options - Additional options + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async replyToThread(threadId, body, replyAll = false, options = {}) { + return this.request("reply_to_thread", { + threadId, + body, + replyAll, + ...options, + }); + } + + /** + * Mark a thread as read. + * @param {string} threadId - The thread ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async markRead(threadId) { + return this.request("mark_read", { threadId }); + } + + /** + * Mark a thread as unread. + * @param {string} threadId - The thread ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async markUnread(threadId) { + return this.request("mark_unread", { threadId }); + } + + /** + * Move a thread to trash. + * @param {string} threadId - The thread ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async moveToTrash(threadId) { + return this.request("move_to_trash", { threadId }); + } + + /** + * Archive a thread. + * @param {string} threadId - The thread ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async moveToArchive(threadId) { + return this.request("move_to_archive", { threadId }); + } + + /** + * Move a thread to inbox. + * @param {string} threadId - The thread ID + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async moveToInbox(threadId) { + return this.request("move_to_inbox", { threadId }); + } + + /** + * Get mailbox statistics. + * @returns {Promise<{success: boolean, data?: object, error?: string}>} + */ + async getMailboxStats() { + return this.request("get_mailbox_stats"); + } +} + +module.exports = new GmailBridge(); +module.exports.GmailBridge = GmailBridge; +module.exports.prepareAttachment = prepareAttachment; +module.exports.parseAttachment = parseAttachment; +module.exports.handleAttachments = handleAttachments; +module.exports.MAX_TOTAL_ATTACHMENT_SIZE = MAX_TOTAL_ATTACHMENT_SIZE; diff --git a/server/utils/agents/aibitat/plugins/gmail/search/gmail-get-inbox.js b/server/utils/agents/aibitat/plugins/gmail/search/gmail-get-inbox.js new file mode 100644 index 00000000..94033bbd --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/search/gmail-get-inbox.js @@ -0,0 +1,104 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailGetInbox = { + name: "gmail-get-inbox", + plugin: function () { + return { + name: "gmail-get-inbox", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Get the inbox emails from Gmail. Returns the inbox emails with the details of the email. " + + "Supports optional query and limit parameters to filter the emails.", + examples: [ + { + prompt: "What's in my inbox?", + call: JSON.stringify({ + query: "", + limit: 10, + }), + }, + { + prompt: "Check my inbox for any emails from John Doe", + call: JSON.stringify({ + query: "from:john.doe@example.com", + limit: 10, + }), + }, + { + prompt: "Get my 5 most recent unread emails", + call: JSON.stringify({ + query: "is:unread newer_than:1d", + limit: 5, + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + query: { + type: "string", + description: + "Optional Gmail search query. Use Gmail query syntax like 'is:inbox', 'is:unread', 'from:email', 'subject:keyword', etc.", + }, + limit: { + type: "number", + description: + "Optional maximum number of results to return (1-50). Defaults to 10.", + default: 10, + }, + }, + required: [], + additionalProperties: false, + }, + handler: async function ({ query = "", limit = 10 }) { + try { + this.super.handlerProps.log(`Using the gmail-get-inbox tool.`); + this.super.introspect( + `${this.caller}: Searching Gmail with query "${query}"` + ); + + let searchQuery = `is:inbox`; + if (query) searchQuery += ` ${query}`; + const result = await gmailLib.search(searchQuery, limit); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Gmail get inbox failed - ${result.error}` + ); + return `Error getting inbox from Gmail: ${result.error}`; + } + + const { threads, resultCount } = result.data; + this.super.introspect( + `${this.caller}: Found ${resultCount} emails in inbox` + ); + + if (resultCount === 0) { + return `No emails found in inbox.`; + } + + const summary = threads + .map( + (t, i) => + `${i + 1}. [${t.isUnread ? "UNREAD" : "READ"}] "${t.subject}" (ID: ${t.id}, ${t.messageCount} messages, Last: ${new Date(t.lastMessageDate).toLocaleString()})` + ) + .join("\n"); + + return `Found ${resultCount} email threads:\n\n${summary}\n\nUse the thread ID with gmail-read-thread to read the full conversation.`; + } catch (e) { + this.super.handlerProps.log( + `gmail-get-inbox error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error getting inbox from Gmail: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/search/gmail-read-thread.js b/server/utils/agents/aibitat/plugins/gmail/search/gmail-read-thread.js new file mode 100644 index 00000000..c03225b2 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/search/gmail-read-thread.js @@ -0,0 +1,114 @@ +const gmailLib = require("../lib.js"); +const { handleAttachments } = require("../lib.js"); + +module.exports.GmailReadThread = { + name: "gmail-read-thread", + plugin: function () { + return { + name: "gmail-read-thread", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Read a full email thread by its ID. Returns all messages in the thread " + + "including sender, recipients, subject, body, date, and attachment information. " + + "Use this after searching to read the full conversation.", + examples: [ + { + prompt: "Read the email thread with ID 18abc123def", + call: JSON.stringify({ + threadId: "18abc123def", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to read.", + }, + }, + required: ["threadId"], + additionalProperties: false, + }, + handler: async function ({ threadId }) { + try { + this.super.handlerProps.log(`Using the gmail-read-thread tool.`); + + if (!threadId) { + return "Error: threadId is required."; + } + + this.super.introspect( + `${this.caller}: Reading Gmail thread ${threadId}` + ); + + const result = await gmailLib.readThread(threadId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to read thread - ${result.error}` + ); + return `Error reading Gmail thread: ${result.error}`; + } + + const thread = result.data; + const labels = thread.labels?.length + ? `Labels: ${thread.labels.join(", ")}` + : "No labels"; + + const { allAttachments, parsedContent: parsedAttachmentContent } = + await handleAttachments(this, thread.messages); + + const messagesFormatted = thread.messages + .map((msg, i) => { + let attachmentInfo = ""; + if (msg.attachments?.length > 0) { + attachmentInfo = `\n Attachments: ${msg.attachments.map((a) => `${a.name} (${a.contentType}, ${(a.size / 1024).toFixed(1)}KB)`).join(", ")}`; + } + return ( + `--- Message ${i + 1} ---\n` + + `From: ${msg.from}\n` + + `To: ${msg.to}\n` + + (msg.cc ? `CC: ${msg.cc}\n` : "") + + `Date: ${new Date(msg.date).toLocaleString()}\n` + + `Subject: ${msg.subject}\n` + + `Status: ${msg.isUnread ? "UNREAD" : "READ"}${msg.isStarred ? ", STARRED" : ""}\n` + + `\n${msg.body}` + + attachmentInfo + ); + }) + .join("\n\n"); + + this.super.introspect( + `${this.caller}: Successfully read thread with ${thread.messageCount} messages` + ); + + return ( + `Thread: "${thread.subject}"\n` + + `Thread ID: ${thread.id}\n` + + `Messages: ${thread.messageCount}\n` + + `Total Attachments: ${allAttachments.length}\n` + + `Status: ${thread.isUnread ? "UNREAD" : "READ"}${thread.isImportant ? ", IMPORTANT" : ""}${thread.hasStarredMessages ? ", HAS STARRED" : ""}\n` + + `Location: ${thread.isInInbox ? "Inbox" : ""}${thread.isInSpam ? "Spam" : ""}${thread.isInTrash ? "Trash" : ""}\n` + + `${labels}\n` + + `Permalink: ${thread.permalink}\n\n` + + messagesFormatted + + parsedAttachmentContent + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-read-thread error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error reading Gmail thread: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/search/gmail-search.js b/server/utils/agents/aibitat/plugins/gmail/search/gmail-search.js new file mode 100644 index 00000000..814be79b --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/search/gmail-search.js @@ -0,0 +1,121 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailSearch = { + name: "gmail-search", + plugin: function () { + return { + name: "gmail-search", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Search emails in Gmail using Gmail query syntax. " + + "Supports full Gmail search including keywords and operators combined. " + + "Common operators: 'is:inbox', 'is:unread', 'is:starred', 'from:email', 'to:email', " + + "'subject:word', 'has:attachment', 'newer_than:7d', 'older_than:1m'. " + + "Combine with search terms: 'is:inbox meeting notes' finds inbox emails containing 'meeting notes'. " + + "Returns thread summaries with ID, subject, date, and unread status.", + examples: [ + { + prompt: "Search for unread emails in my inbox about the project", + call: JSON.stringify({ + query: "is:inbox is:unread project update", + limit: 10, + }), + }, + { + prompt: "Find emails from john@example.com about meetings", + call: JSON.stringify({ + query: "from:john@example.com meeting", + limit: 20, + }), + }, + { + prompt: + "Search for emails with attachments from last week about invoices", + call: JSON.stringify({ + query: "has:attachment newer_than:7d invoice", + limit: 15, + }), + }, + { + prompt: "Find starred emails about budget", + call: JSON.stringify({ + query: "is:starred budget", + limit: 10, + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + query: { + type: "string", + description: + "Gmail search query. Use Gmail query syntax like 'is:inbox', 'is:unread', 'from:email', 'subject:keyword', etc.", + }, + limit: { + type: "number", + description: + "Maximum number of results to return (1-50). Defaults to 10.", + default: 10, + }, + start: { + type: "number", + description: "Starting offset for pagination. Defaults to 0.", + default: 0, + }, + }, + required: ["query"], + additionalProperties: false, + }, + handler: async function ({ + query = "is:inbox", + limit = 10, + start = 0, + }) { + try { + this.super.handlerProps.log(`Using the gmail-search tool.`); + this.super.introspect( + `${this.caller}: Searching Gmail with query "${query}"` + ); + + const result = await gmailLib.search(query, limit, start); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Gmail search failed - ${result.error}` + ); + return `Error searching Gmail: ${result.error}`; + } + + const { threads, resultCount } = result.data; + this.super.introspect( + `${this.caller}: Found ${resultCount} email threads matching query` + ); + + if (resultCount === 0) { + return `No emails found matching query "${query}".`; + } + + const summary = threads + .map( + (t, i) => + `${i + 1}. [${t.isUnread ? "UNREAD" : "READ"}] "${t.subject}" (ID: ${t.id}, ${t.messageCount} messages, Last: ${new Date(t.lastMessageDate).toLocaleString()})` + ) + .join("\n"); + + return `Found ${resultCount} email threads:\n\n${summary}\n\nUse the thread ID with gmail-read-thread to read the full conversation.`; + } catch (e) { + this.super.handlerProps.log(`gmail-search error: ${e.message}`); + this.super.introspect(`Error: ${e.message}`); + return `Error searching Gmail: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/send/gmail-reply-to-thread.js b/server/utils/agents/aibitat/plugins/gmail/send/gmail-reply-to-thread.js new file mode 100644 index 00000000..56cf5f9c --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/send/gmail-reply-to-thread.js @@ -0,0 +1,238 @@ +const gmailLib = require("../lib.js"); +const { prepareAttachment, MAX_TOTAL_ATTACHMENT_SIZE } = require("../lib.js"); +const { humanFileSize } = require("../../../../../helpers"); + +module.exports.GmailReplyToThread = { + name: "gmail-reply-to-thread", + plugin: function () { + return { + name: "gmail-reply-to-thread", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Reply to an existing email thread immediately. " + + "This action sends the reply right away and cannot be undone. " + + "For composing replies that need review before sending, use gmail-create-draft-reply instead. " + + "Supports file attachments via absolute file paths (max 20MB total for all attachments combined).", + examples: [ + { + prompt: "Reply to thread 18abc123def", + call: JSON.stringify({ + threadId: "18abc123def", + body: "Thank you for your email. I've reviewed the proposal and have some feedback.", + replyAll: false, + }), + }, + { + prompt: "Reply all to the thread", + call: JSON.stringify({ + threadId: "18abc123def", + body: "Thanks everyone. I agree with the proposed timeline.", + replyAll: true, + }), + }, + { + prompt: "Reply with an attachment", + call: JSON.stringify({ + threadId: "18abc123def", + body: "Please find the requested document attached.", + attachments: ["/Users/me/Documents/document.pdf"], + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to reply to.", + }, + body: { + type: "string", + description: "Plain text reply body content.", + }, + replyAll: { + type: "boolean", + description: + "Whether to reply to all recipients. Defaults to false (reply to sender only).", + default: false, + }, + cc: { + type: "string", + description: + "Additional CC recipient email address(es). Optional.", + }, + bcc: { + type: "string", + description: "BCC recipient email address(es). Optional.", + }, + htmlBody: { + type: "string", + description: "HTML version of the reply body. Optional.", + }, + attachments: { + type: "array", + items: { type: "string" }, + description: + "Array of absolute file paths to attach to the reply.", + }, + }, + required: ["threadId", "body"], + additionalProperties: false, + }, + handler: async function ({ + threadId, + body, + replyAll = false, + cc, + bcc, + htmlBody, + attachments, + }) { + try { + this.super.handlerProps.log( + `Using the gmail-reply-to-thread tool.` + ); + + if (!threadId || !body) { + return "Error: 'threadId' and 'body' are required."; + } + + const preparedAttachments = []; + const attachmentSummaries = []; + let totalAttachmentSize = 0; + + if (Array.isArray(attachments) && attachments.length > 0) { + this.super.introspect( + `${this.caller}: Validating ${attachments.length} attachment(s)...` + ); + + for (const filePath of attachments) { + const result = prepareAttachment(filePath); + if (!result.success) { + this.super.introspect( + `${this.caller}: Attachment validation failed - ${result.error}` + ); + return `Error with attachment: ${result.error}`; + } + + totalAttachmentSize += result.fileInfo.size; + if (totalAttachmentSize > MAX_TOTAL_ATTACHMENT_SIZE) { + const totalFormatted = humanFileSize( + totalAttachmentSize, + true + ); + this.super.introspect( + `${this.caller}: Total attachment size (${totalFormatted}) exceeds 20MB limit` + ); + return `Error: Total attachment size (${totalFormatted}) exceeds the 20MB limit. Please reduce the number or size of attachments.`; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + fileName: result.fileInfo.name, + fileSize: result.fileInfo.sizeFormatted, + filePath: result.fileInfo.path, + }, + description: + `Attach file "${result.fileInfo.name}" (${result.fileInfo.sizeFormatted}) to reply? ` + + `This file will be sent immediately.`, + }); + + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected attaching "${result.fileInfo.name}"` + ); + return `Attachment rejected by user: ${result.fileInfo.name}. ${approval.message || ""}`; + } + } + + preparedAttachments.push(result.attachment); + attachmentSummaries.push( + `${result.fileInfo.name} (${result.fileInfo.sizeFormatted})` + ); + this.super.introspect( + `${this.caller}: Prepared attachment "${result.fileInfo.name}"` + ); + } + } + + if (this.super.requestToolApproval) { + const attachmentNote = + preparedAttachments.length > 0 + ? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}` + : ""; + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + threadId, + replyAll, + attachmentCount: preparedAttachments.length, + }, + description: `Reply to thread "${threadId}"${replyAll ? " (reply all)" : ""}${attachmentNote} - This will send immediately`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Replying to thread ${threadId}${replyAll ? " (reply all)" : ""}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}` + ); + + const options = {}; + if (cc) options.cc = cc; + if (bcc) options.bcc = bcc; + if (htmlBody) options.htmlBody = htmlBody; + if (preparedAttachments.length > 0) { + options.attachments = preparedAttachments; + } + + const result = await gmailLib.replyToThread( + threadId, + body, + replyAll, + options + ); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to reply to thread - ${result.error}` + ); + return `Error replying to thread: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully replied to thread ${threadId}` + ); + + return ( + `Successfully replied to thread:\n` + + `Thread ID: ${threadId}\n` + + `Reply Type: ${replyAll ? "Reply All" : "Reply"}\n` + + (preparedAttachments.length > 0 + ? `Attachments: ${attachmentSummaries.join(", ")}\n` + : "") + + `\nThe reply has been sent.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-reply-to-thread error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error replying to thread: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/send/gmail-send-email.js b/server/utils/agents/aibitat/plugins/gmail/send/gmail-send-email.js new file mode 100644 index 00000000..7b1cdde3 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/send/gmail-send-email.js @@ -0,0 +1,242 @@ +const gmailLib = require("../lib.js"); +const { prepareAttachment, MAX_TOTAL_ATTACHMENT_SIZE } = require("../lib.js"); +const { humanFileSize } = require("../../../../../helpers"); + +module.exports.GmailSendEmail = { + name: "gmail-send-email", + plugin: function () { + return { + name: "gmail-send-email", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Send an email immediately through Gmail. " + + "This action sends the email right away and cannot be undone. " + + "For composing emails that need review before sending, use gmail-create-draft instead.", + examples: [ + { + prompt: "Send an email to john@example.com about the project", + call: JSON.stringify({ + to: "john@example.com", + subject: "Project Update", + body: "Hi John,\n\nHere's the latest update on the project.\n\nBest regards", + }), + }, + { + prompt: "Send an email with CC recipients", + call: JSON.stringify({ + to: "john@example.com", + subject: "Meeting Notes", + body: "Please find the meeting notes attached.", + cc: "manager@example.com, team@example.com", + }), + }, + { + prompt: "Send an email with an attachment", + call: JSON.stringify({ + to: "john@example.com", + subject: "Report", + body: "Please find the report attached.", + attachments: ["/Users/me/Documents/report.pdf"], + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + to: { + type: "string", + description: + "Recipient email address(es). Multiple addresses can be comma-separated.", + }, + subject: { + type: "string", + description: "Email subject line.", + }, + body: { + type: "string", + description: "Plain text email body content.", + }, + cc: { + type: "string", + description: "CC recipient email address(es). Optional.", + }, + bcc: { + type: "string", + description: "BCC recipient email address(es). Optional.", + }, + htmlBody: { + type: "string", + description: "HTML version of the email body. Optional.", + }, + replyTo: { + type: "string", + description: "Reply-to email address. Optional.", + }, + attachments: { + type: "array", + items: { type: "string" }, + description: + "Array of absolute file paths to attach to the email.", + }, + }, + required: ["to", "subject", "body"], + additionalProperties: false, + }, + handler: async function ({ + to, + subject, + body, + cc, + bcc, + htmlBody, + replyTo, + attachments, + }) { + try { + this.super.handlerProps.log(`Using the gmail-send-email tool.`); + + if (!to || !subject) { + return "Error: 'to' and 'subject' are required."; + } + + const preparedAttachments = []; + const attachmentSummaries = []; + let totalAttachmentSize = 0; + + if (Array.isArray(attachments) && attachments.length > 0) { + this.super.introspect( + `${this.caller}: Validating ${attachments.length} attachment(s)...` + ); + + for (const filePath of attachments) { + const result = prepareAttachment(filePath); + if (!result.success) { + this.super.introspect( + `${this.caller}: Attachment validation failed - ${result.error}` + ); + return `Error with attachment: ${result.error}`; + } + + totalAttachmentSize += result.fileInfo.size; + if (totalAttachmentSize > MAX_TOTAL_ATTACHMENT_SIZE) { + const totalFormatted = humanFileSize( + totalAttachmentSize, + true + ); + this.super.introspect( + `${this.caller}: Total attachment size (${totalFormatted}) exceeds 20MB limit` + ); + return `Error: Total attachment size (${totalFormatted}) exceeds the 20MB limit. Please reduce the number or size of attachments.`; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + fileName: result.fileInfo.name, + fileSize: result.fileInfo.sizeFormatted, + filePath: result.fileInfo.path, + }, + description: + `Attach file "${result.fileInfo.name}" (${result.fileInfo.sizeFormatted}) to email? ` + + `This file will be sent to ${to}.`, + }); + + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected attaching "${result.fileInfo.name}"` + ); + return `Attachment rejected by user: ${result.fileInfo.name}. ${approval.message || ""}`; + } + } + + preparedAttachments.push(result.attachment); + attachmentSummaries.push( + `${result.fileInfo.name} (${result.fileInfo.sizeFormatted})` + ); + this.super.introspect( + `${this.caller}: Prepared attachment "${result.fileInfo.name}"` + ); + } + } + + if (this.super.requestToolApproval) { + const attachmentNote = + preparedAttachments.length > 0 + ? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}` + : ""; + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { + to, + subject, + attachmentCount: preparedAttachments.length, + }, + description: `Send email to "${to}" with subject "${subject}"${attachmentNote} - This will send immediately`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Sending email to ${to}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}` + ); + + const options = {}; + if (cc) options.cc = cc; + if (bcc) options.bcc = bcc; + if (htmlBody) options.htmlBody = htmlBody; + if (replyTo) options.replyTo = replyTo; + if (preparedAttachments.length > 0) { + options.attachments = preparedAttachments; + } + + const result = await gmailLib.sendEmail( + to, + subject, + body, + options + ); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to send email - ${result.error}` + ); + return `Error sending email: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully sent email to ${to}` + ); + + return ( + `Successfully sent email:\n` + + `To: ${to}\n` + + `Subject: ${subject}\n` + + (cc ? `CC: ${cc}\n` : "") + + (preparedAttachments.length > 0 + ? `Attachments: ${attachmentSummaries.join(", ")}\n` + : "") + + `\nThe email has been sent.` + ); + } catch (e) { + this.super.handlerProps.log( + `gmail-send-email error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error sending email: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-read.js b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-read.js new file mode 100644 index 00000000..3b95ac4b --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-read.js @@ -0,0 +1,87 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailMarkRead = { + name: "gmail-mark-read", + plugin: function () { + return { + name: "gmail-mark-read", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Mark an email thread as read in Gmail. " + + "This will mark all messages in the thread as read.", + examples: [ + { + prompt: "Mark thread 18abc123def as read", + call: JSON.stringify({ + threadId: "18abc123def", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to mark as read.", + }, + }, + required: ["threadId"], + additionalProperties: false, + }, + handler: async function ({ threadId }) { + try { + this.super.handlerProps.log(`Using the gmail-mark-read tool.`); + + if (!threadId) { + return "Error: 'threadId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { threadId }, + description: `Mark Gmail thread "${threadId}" as read`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Marking thread ${threadId} as read` + ); + + const result = await gmailLib.markRead(threadId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to mark thread as read - ${result.error}` + ); + return `Error marking thread as read: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully marked thread ${threadId} as read` + ); + + return `Successfully marked thread ${threadId} as read.`; + } catch (e) { + this.super.handlerProps.log( + `gmail-mark-read error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error marking thread as read: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-unread.js b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-unread.js new file mode 100644 index 00000000..ada7013e --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-mark-unread.js @@ -0,0 +1,87 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailMarkUnread = { + name: "gmail-mark-unread", + plugin: function () { + return { + name: "gmail-mark-unread", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Mark an email thread as unread in Gmail. " + + "This will mark the thread as unread so it appears as a new message.", + examples: [ + { + prompt: "Mark thread 18abc123def as unread", + call: JSON.stringify({ + threadId: "18abc123def", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to mark as unread.", + }, + }, + required: ["threadId"], + additionalProperties: false, + }, + handler: async function ({ threadId }) { + try { + this.super.handlerProps.log(`Using the gmail-mark-unread tool.`); + + if (!threadId) { + return "Error: 'threadId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { threadId }, + description: `Mark Gmail thread "${threadId}" as unread`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Marking thread ${threadId} as unread` + ); + + const result = await gmailLib.markUnread(threadId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to mark thread as unread - ${result.error}` + ); + return `Error marking thread as unread: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully marked thread ${threadId} as unread` + ); + + return `Successfully marked thread ${threadId} as unread.`; + } catch (e) { + this.super.handlerProps.log( + `gmail-mark-unread error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error marking thread as unread: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-archive.js b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-archive.js new file mode 100644 index 00000000..b275b907 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-archive.js @@ -0,0 +1,89 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailMoveToArchive = { + name: "gmail-move-to-archive", + plugin: function () { + return { + name: "gmail-move-to-archive", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Archive an email thread in Gmail. " + + "The thread will be removed from inbox but can still be found in All Mail or by searching.", + examples: [ + { + prompt: "Archive thread 18abc123def", + call: JSON.stringify({ + threadId: "18abc123def", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to archive.", + }, + }, + required: ["threadId"], + additionalProperties: false, + }, + handler: async function ({ threadId }) { + try { + this.super.handlerProps.log( + `Using the gmail-move-to-archive tool.` + ); + + if (!threadId) { + return "Error: 'threadId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { threadId }, + description: `Archive Gmail thread "${threadId}"`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Archiving thread ${threadId}` + ); + + const result = await gmailLib.moveToArchive(threadId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to archive thread - ${result.error}` + ); + return `Error archiving thread: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully archived thread ${threadId}` + ); + + return `Successfully archived thread ${threadId}. It can still be found in All Mail or by searching.`; + } catch (e) { + this.super.handlerProps.log( + `gmail-move-to-archive error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error archiving thread: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-inbox.js b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-inbox.js new file mode 100644 index 00000000..76b7b866 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-inbox.js @@ -0,0 +1,89 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailMoveToInbox = { + name: "gmail-move-to-inbox", + plugin: function () { + return { + name: "gmail-move-to-inbox", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Move an email thread back to inbox in Gmail. " + + "Use this to unarchive a thread or move it from other locations to inbox.", + examples: [ + { + prompt: "Move thread 18abc123def to inbox", + call: JSON.stringify({ + threadId: "18abc123def", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to move to inbox.", + }, + }, + required: ["threadId"], + additionalProperties: false, + }, + handler: async function ({ threadId }) { + try { + this.super.handlerProps.log( + `Using the gmail-move-to-inbox tool.` + ); + + if (!threadId) { + return "Error: 'threadId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { threadId }, + description: `Move Gmail thread "${threadId}" to inbox`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Moving thread ${threadId} to inbox` + ); + + const result = await gmailLib.moveToInbox(threadId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to move thread to inbox - ${result.error}` + ); + return `Error moving thread to inbox: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully moved thread ${threadId} to inbox` + ); + + return `Successfully moved thread ${threadId} to inbox.`; + } catch (e) { + this.super.handlerProps.log( + `gmail-move-to-inbox error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error moving thread to inbox: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-trash.js b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-trash.js new file mode 100644 index 00000000..fe65ae00 --- /dev/null +++ b/server/utils/agents/aibitat/plugins/gmail/threads/gmail-move-to-trash.js @@ -0,0 +1,89 @@ +const gmailLib = require("../lib.js"); + +module.exports.GmailMoveToTrash = { + name: "gmail-move-to-trash", + plugin: function () { + return { + name: "gmail-move-to-trash", + setup(aibitat) { + aibitat.function({ + super: aibitat, + name: this.name, + description: + "Move an email thread to trash in Gmail. " + + "The thread can be recovered from trash within 30 days.", + examples: [ + { + prompt: "Move thread 18abc123def to trash", + call: JSON.stringify({ + threadId: "18abc123def", + }), + }, + ], + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + properties: { + threadId: { + type: "string", + description: "The Gmail thread ID to move to trash.", + }, + }, + required: ["threadId"], + additionalProperties: false, + }, + handler: async function ({ threadId }) { + try { + this.super.handlerProps.log( + `Using the gmail-move-to-trash tool.` + ); + + if (!threadId) { + return "Error: 'threadId' is required."; + } + + if (this.super.requestToolApproval) { + const approval = await this.super.requestToolApproval({ + skillName: this.name, + payload: { threadId }, + description: `Move Gmail thread "${threadId}" to trash`, + }); + if (!approval.approved) { + this.super.introspect( + `${this.caller}: User rejected the ${this.name} request.` + ); + return approval.message; + } + } + + this.super.introspect( + `${this.caller}: Moving thread ${threadId} to trash` + ); + + const result = await gmailLib.moveToTrash(threadId); + + if (!result.success) { + this.super.introspect( + `${this.caller}: Failed to move thread to trash - ${result.error}` + ); + return `Error moving thread to trash: ${result.error}`; + } + + this.super.introspect( + `${this.caller}: Successfully moved thread ${threadId} to trash` + ); + + return `Successfully moved thread ${threadId} to trash. It can be recovered within 30 days.`; + } catch (e) { + this.super.handlerProps.log( + `gmail-move-to-trash error: ${e.message}` + ); + this.super.introspect(`Error: ${e.message}`); + return `Error moving thread to trash: ${e.message}`; + } + }, + }); + }, + }; + }, +}; diff --git a/server/utils/agents/aibitat/plugins/index.js b/server/utils/agents/aibitat/plugins/index.js index 22c0c0df..fb9a2ac4 100644 --- a/server/utils/agents/aibitat/plugins/index.js +++ b/server/utils/agents/aibitat/plugins/index.js @@ -8,6 +8,7 @@ const { rechart } = require("./rechart.js"); const { sqlAgent } = require("./sql-agent/index.js"); const { filesystemAgent } = require("./filesystem/index.js"); const { createFilesAgent } = require("./create-files/index.js"); +const { gmailAgent } = require("./gmail/index.js"); module.exports = { webScraping, @@ -20,6 +21,7 @@ module.exports = { sqlAgent, filesystemAgent, createFilesAgent, + gmailAgent, // Plugin name aliases so they can be pulled by slug as well. [webScraping.name]: webScraping, @@ -32,4 +34,5 @@ module.exports = { [sqlAgent.name]: sqlAgent, [filesystemAgent.name]: filesystemAgent, [createFilesAgent.name]: createFilesAgent, + [gmailAgent.name]: gmailAgent, }; diff --git a/server/utils/agents/defaults.js b/server/utils/agents/defaults.js index c51bfc84..5700b4e5 100644 --- a/server/utils/agents/defaults.js +++ b/server/utils/agents/defaults.js @@ -90,6 +90,15 @@ async function agentSkillsFromSystemSettings() { [] ); + // Load disabled gmail sub-skills + const _disabledGmailSkills = safeJsonParse( + await SystemSettings.getValueOrFallback( + { label: "disabled_gmail_skills" }, + "[]" + ), + [] + ); + // Load non-imported built-in skills that are configurable. const _setting = safeJsonParse( await SystemSettings.getValueOrFallback( @@ -98,8 +107,16 @@ async function agentSkillsFromSystemSettings() { ), [] ); - _setting.forEach((skillName) => { - if (!AgentPlugins.hasOwnProperty(skillName)) return; + + // Pre-check gmail availability once (async) to avoid await inside loop + let gmailAvailable = false; + if (_setting.includes("gmail-agent")) { + const gmailTool = require("./aibitat/plugins/gmail/lib"); + gmailAvailable = await gmailTool.GmailBridge.isToolAvailable(); + } + + for (const skillName of _setting) { + if (!AgentPlugins.hasOwnProperty(skillName)) continue; // This is a plugin module with many sub-children plugins who // need to be named via `${parent}#${child}` naming convention @@ -125,16 +142,26 @@ async function agentSkillsFromSystemSettings() { if (_disabledCreateFilesSkills.includes(subPlugin.name)) continue; } + /** + * If the gmail tool is not available (multi-user mode or missing config), + * or the sub-skill is explicitly disabled, skip it. + * Gmail integration is only available in single-user mode for security reasons. + */ + if (skillName === "gmail-agent") { + if (!gmailAvailable) continue; + if (_disabledGmailSkills.includes(subPlugin.name)) continue; + } + systemFunctions.push( `${AgentPlugins[skillName].name}#${subPlugin.name}` ); } - return; + continue; } // This is normal single-stage plugin systemFunctions.push(AgentPlugins[skillName].name); - }); + } return systemFunctions; }