From a838f62b41714fe27325deaa86f0671290c30438 Mon Sep 17 00:00:00 2001 From: Serhii Dziupin Date: Wed, 12 Aug 2026 10:14:20 +0000 Subject: [PATCH] feat(integrations): add Discord/Telegram Coming soon placeholders Show greyed non-interactive messenger cards with a Coming soon badge (matching integration card chrome, no expandable controls), and refresh third-party statuses immediately after Apply & Restart clears pending plugin restarts. Co-authored-by: Serhii Dziupin --- packages/ui/src/components/icon/sprite.ts | 1 + .../ComingSoonMessengersSection.tsx | 74 +++++++++++++++++++ .../integrations/IntegrationsPage.tsx | 2 + .../ThirdPartyIntegrationsSection.tsx | 25 +++++++ .../third-party-integrations.i18n.test.ts | 3 + .../messages/third-party-integrations.i18n.ts | 66 +++++++++++++++++ packages/ui/src/lib/settings/metadata.ts | 2 +- packages/ui/src/lib/settings/search.test.ts | 11 +++ packages/ui/src/lib/settings/search.ts | 20 +++++ 9 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/components/sections/integrations/ComingSoonMessengersSection.tsx diff --git a/packages/ui/src/components/icon/sprite.ts b/packages/ui/src/components/icon/sprite.ts index b93478e6..5a7d7db1 100644 --- a/packages/ui/src/components/icon/sprite.ts +++ b/packages/ui/src/components/icon/sprite.ts @@ -227,6 +227,7 @@ export const iconSpriteData = { "target": ``, "target-fill": ``, "task": ``, + "telegram-fill": ``, "terminal": ``, "terminal-box": ``, "terminal-window": ``, diff --git a/packages/ui/src/components/sections/integrations/ComingSoonMessengersSection.tsx b/packages/ui/src/components/sections/integrations/ComingSoonMessengersSection.tsx new file mode 100644 index 00000000..ab2693ed --- /dev/null +++ b/packages/ui/src/components/sections/integrations/ComingSoonMessengersSection.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Icon } from '@/components/icon/Icon'; +import { SettingsSection } from '@/components/sections/shared/SettingsSection'; +import type { IconName } from '@/components/icon/icons'; +import { useI18n, type I18nKey } from '@/lib/i18n'; +import { cn } from '@/lib/utils'; + +type ComingSoonMessenger = { + id: 'discord' | 'telegram'; + icon: IconName; + brandClassName: string; + nameKey: I18nKey; + descriptionKey: I18nKey; +}; + +const COMING_SOON_MESSENGERS: readonly ComingSoonMessenger[] = [ + { + id: 'discord', + icon: 'discord-fill', + brandClassName: 'text-[#5865F2]', + nameKey: 'settings.integrations.messengers.discord.name', + descriptionKey: 'settings.integrations.messengers.discord.description', + }, + { + id: 'telegram', + icon: 'telegram-fill', + brandClassName: 'text-[#2AABEE]', + nameKey: 'settings.integrations.messengers.telegram.name', + descriptionKey: 'settings.integrations.messengers.telegram.description', + }, +] as const; + +/** + * Non-interactive Discord/Telegram placeholders — same card chrome as live + * integrations, greyed out, with a Coming soon badge and no expandable body. + */ +export const ComingSoonMessengersSection: React.FC = () => { + const { t } = useI18n(); + + return ( + + {COMING_SOON_MESSENGERS.map((messenger) => ( +
+
+ +
+
+
{t(messenger.nameKey)}
+

+ {t(messenger.descriptionKey)} +

+
+ + {t('settings.common.state.comingSoon')} + +
+ ))} +
+ ); +}; diff --git a/packages/ui/src/components/sections/integrations/IntegrationsPage.tsx b/packages/ui/src/components/sections/integrations/IntegrationsPage.tsx index fe7f00e9..f656adcc 100644 --- a/packages/ui/src/components/sections/integrations/IntegrationsPage.tsx +++ b/packages/ui/src/components/sections/integrations/IntegrationsPage.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLayout'; import { useI18n } from '@/lib/i18n'; +import { ComingSoonMessengersSection } from './ComingSoonMessengersSection'; import { ThirdPartyIntegrationsSection } from './ThirdPartyIntegrationsSection'; interface IntegrationsPageProps { @@ -20,6 +21,7 @@ export const IntegrationsPage: React.FC = ({ description={t('settings.page.integrations.description')} showSaveStatus={false} > + state.changes.filter((change) => change.scope === 'plugins').length, + ); + const isApplyingRestart = usePendingOpenCodeRestartStore((state) => state.isApplying); + const previousPluginRestartCountRef = React.useRef(pendingPluginRestartCount); + + // When deferred plugin restarts are applied (pending plugins scope clears), drop + // local restart/unavailable flags and reload so statuses update immediately. + React.useEffect(() => { + const previousCount = previousPluginRestartCountRef.current; + previousPluginRestartCountRef.current = pendingPluginRestartCount; + + if (isApplyingRestart) { + return; + } + if (previousCount <= 0 || pendingPluginRestartCount > 0) { + return; + } + + setRestartRequiredIds(new Set()); + setProviderUnavailableIds(new Set()); + void refresh(); + }, [isApplyingRestart, pendingPluginRestartCount, refresh]); + const setRestartRequired = React.useCallback((pluginId: string, required: boolean) => { setRestartRequiredIds((current) => { const next = new Set(current); diff --git a/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.test.ts b/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.test.ts index 457362f9..03a57e39 100644 --- a/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.test.ts +++ b/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.test.ts @@ -6,6 +6,9 @@ const locales = ['en', 'de', 'fr', 'es', 'ja', 'pt-BR', 'uk', 'ko', 'pl', 'zh-CN const requiredKeys = [ 'settings.page.integrations.title', 'settings.page.integrations.description', + 'settings.integrations.messengers.title', + 'settings.integrations.messengers.discord.name', + 'settings.integrations.messengers.telegram.name', 'settings.integrations.thirdParty.title', 'settings.integrations.thirdParty.actions.install', 'settings.integrations.thirdParty.actions.update', diff --git a/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.ts b/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.ts index 1df8a4c1..60cb32dc 100644 --- a/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.ts +++ b/packages/ui/src/lib/i18n/messages/third-party-integrations.i18n.ts @@ -3,6 +3,12 @@ export const thirdPartyIntegrationI18n = { en: { 'settings.page.integrations.title': 'Integrations', 'settings.page.integrations.description': 'Connect third-party providers to OpenCode.', + 'settings.integrations.messengers.title': 'Messengers', + 'settings.integrations.messengers.info': 'Chat with OpenChamber from Discord or Telegram. These bridges are not available yet.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Connect a Discord bot to chat with OpenChamber.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Connect a Telegram bot to chat with OpenChamber.', 'settings.integrations.thirdParty.title': 'Third-party integrations', 'settings.integrations.thirdParty.info': 'Install and manage supported OpenCode provider plugins. Setup opens the provider’s existing authentication settings.', 'settings.integrations.thirdParty.actions.install': 'Install', @@ -39,6 +45,12 @@ export const thirdPartyIntegrationI18n = { de: { 'settings.page.integrations.title': 'Integrationen', 'settings.page.integrations.description': 'Verbinde Drittanbieter-Provider mit OpenCode.', + 'settings.integrations.messengers.title': 'Messenger', + 'settings.integrations.messengers.info': 'Chatte mit OpenChamber über Discord oder Telegram. Diese Bridges sind noch nicht verfügbar.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Verbinde einen Discord-Bot, um mit OpenChamber zu chatten.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Verbinde einen Telegram-Bot, um mit OpenChamber zu chatten.', 'settings.integrations.thirdParty.title': 'Drittanbieter-Integrationen', 'settings.integrations.thirdParty.info': 'Installiere und verwalte unterstützte OpenCode-Provider-Plugins. Die Einrichtung öffnet die bestehenden Authentifizierungseinstellungen des Providers.', 'settings.integrations.thirdParty.actions.install': 'Installieren', @@ -75,6 +87,12 @@ export const thirdPartyIntegrationI18n = { fr: { 'settings.page.integrations.title': 'Intégrations', 'settings.page.integrations.description': 'Connectez des fournisseurs tiers à OpenCode.', + 'settings.integrations.messengers.title': 'Messagers', + 'settings.integrations.messengers.info': 'Discutez avec OpenChamber depuis Discord ou Telegram. Ces ponts ne sont pas encore disponibles.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Connectez un bot Discord pour discuter avec OpenChamber.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Connectez un bot Telegram pour discuter avec OpenChamber.', 'settings.integrations.thirdParty.title': 'Intégrations tierces', 'settings.integrations.thirdParty.info': 'Installez et gérez les plugins de fournisseurs OpenCode pris en charge. La configuration ouvre les réglages d’authentification existants du fournisseur.', 'settings.integrations.thirdParty.actions.install': 'Installer', @@ -111,6 +129,12 @@ export const thirdPartyIntegrationI18n = { es: { 'settings.page.integrations.title': 'Integraciones', 'settings.page.integrations.description': 'Conecta proveedores de terceros a OpenCode.', + 'settings.integrations.messengers.title': 'Mensajeros', + 'settings.integrations.messengers.info': 'Chatea con OpenChamber desde Discord o Telegram. Estos puentes aún no están disponibles.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Conecta un bot de Discord para chatear con OpenChamber.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Conecta un bot de Telegram para chatear con OpenChamber.', 'settings.integrations.thirdParty.title': 'Integraciones de terceros', 'settings.integrations.thirdParty.info': 'Instala y administra los plugins de proveedores OpenCode compatibles. La configuración abre los ajustes de autenticación existentes del proveedor.', 'settings.integrations.thirdParty.actions.install': 'Instalar', @@ -147,6 +171,12 @@ export const thirdPartyIntegrationI18n = { ja: { 'settings.page.integrations.title': '連携', 'settings.page.integrations.description': 'サードパーティープロバイダーを OpenCode に接続します。', + 'settings.integrations.messengers.title': 'メッセンジャー', + 'settings.integrations.messengers.info': 'Discord または Telegram から OpenChamber とチャットできます。これらの連携はまだ利用できません。', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Discord ボットを接続して OpenChamber とチャットします。', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Telegram ボットを接続して OpenChamber とチャットします。', 'settings.integrations.thirdParty.title': 'サードパーティー連携', 'settings.integrations.thirdParty.info': '対応する OpenCode プロバイダープラグインをインストールして管理します。設定ではプロバイダーの既存の認証設定を開きます。', 'settings.integrations.thirdParty.actions.install': 'インストール', @@ -183,6 +213,12 @@ export const thirdPartyIntegrationI18n = { ko: { 'settings.page.integrations.title': '통합', 'settings.page.integrations.description': '서드파티 프로바이더를 OpenCode에 연결합니다.', + 'settings.integrations.messengers.title': '메신저', + 'settings.integrations.messengers.info': 'Discord 또는 Telegram에서 OpenChamber와 채팅하세요. 이 브리지는 아직 사용할 수 없습니다.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Discord 봇을 연결해 OpenChamber와 채팅하세요.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Telegram 봇을 연결해 OpenChamber와 채팅하세요.', 'settings.integrations.thirdParty.title': '서드파티 통합', 'settings.integrations.thirdParty.info': '지원되는 OpenCode 프로바이더 플러그인을 설치하고 관리합니다. 설정을 누르면 프로바이더의 기존 인증 설정이 열립니다.', 'settings.integrations.thirdParty.actions.install': '설치', @@ -219,6 +255,12 @@ export const thirdPartyIntegrationI18n = { pl: { 'settings.page.integrations.title': 'Integracje', 'settings.page.integrations.description': 'Połącz zewnętrznych dostawców z OpenCode.', + 'settings.integrations.messengers.title': 'Komunikatory', + 'settings.integrations.messengers.info': 'Czatuj z OpenChamber przez Discord lub Telegram. Te mosty nie są jeszcze dostępne.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Połącz bota Discord, aby czatować z OpenChamber.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Połącz bota Telegram, aby czatować z OpenChamber.', 'settings.integrations.thirdParty.title': 'Integracje zewnętrzne', 'settings.integrations.thirdParty.info': 'Instaluj i zarządzaj obsługiwanymi wtyczkami dostawców OpenCode. Konfiguracja otwiera istniejące ustawienia uwierzytelniania dostawcy.', 'settings.integrations.thirdParty.actions.install': 'Zainstaluj', @@ -255,6 +297,12 @@ export const thirdPartyIntegrationI18n = { 'pt-BR': { 'settings.page.integrations.title': 'Integrações', 'settings.page.integrations.description': 'Conecte provedores de terceiros ao OpenCode.', + 'settings.integrations.messengers.title': 'Mensageiros', + 'settings.integrations.messengers.info': 'Converse com o OpenChamber pelo Discord ou Telegram. Essas pontes ainda não estão disponíveis.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Conecte um bot do Discord para conversar com o OpenChamber.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Conecte um bot do Telegram para conversar com o OpenChamber.', 'settings.integrations.thirdParty.title': 'Integrações de terceiros', 'settings.integrations.thirdParty.info': 'Instale e gerencie plugins compatíveis de provedores do OpenCode. A configuração abre as definições de autenticação existentes do provedor.', 'settings.integrations.thirdParty.actions.install': 'Instalar', @@ -291,6 +339,12 @@ export const thirdPartyIntegrationI18n = { uk: { 'settings.page.integrations.title': 'Інтеграції', 'settings.page.integrations.description': 'Підключайте сторонніх провайдерів до OpenCode.', + 'settings.integrations.messengers.title': 'Месенджери', + 'settings.integrations.messengers.info': 'Спілкуйтеся з OpenChamber у Discord або Telegram. Ці мости ще недоступні.', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': 'Підключіть бота Discord, щоб спілкуватися з OpenChamber.', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': 'Підключіть бота Telegram, щоб спілкуватися з OpenChamber.', 'settings.integrations.thirdParty.title': 'Сторонні інтеграції', 'settings.integrations.thirdParty.info': 'Встановлюйте й керуйте підтримуваними плагінами провайдерів OpenCode. Налаштування відкриває наявні параметри автентифікації провайдера.', 'settings.integrations.thirdParty.actions.install': 'Встановити', @@ -327,6 +381,12 @@ export const thirdPartyIntegrationI18n = { 'zh-CN': { 'settings.page.integrations.title': '集成', 'settings.page.integrations.description': '将第三方提供商连接到 OpenCode。', + 'settings.integrations.messengers.title': '即时通讯', + 'settings.integrations.messengers.info': '通过 Discord 或 Telegram 与 OpenChamber 聊天。这些桥接尚不可用。', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': '连接 Discord 机器人以与 OpenChamber 聊天。', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': '连接 Telegram 机器人以与 OpenChamber 聊天。', 'settings.integrations.thirdParty.title': '第三方集成', 'settings.integrations.thirdParty.info': '安装和管理受支持的 OpenCode 提供商插件。设置会打开该提供商已有的身份验证设置。', 'settings.integrations.thirdParty.actions.install': '安装', @@ -363,6 +423,12 @@ export const thirdPartyIntegrationI18n = { 'zh-TW': { 'settings.page.integrations.title': '整合', 'settings.page.integrations.description': '將第三方供應商連接到 OpenCode。', + 'settings.integrations.messengers.title': '即時通訊', + 'settings.integrations.messengers.info': '透過 Discord 或 Telegram 與 OpenChamber 聊天。這些橋接尚不可用。', + 'settings.integrations.messengers.discord.name': 'Discord', + 'settings.integrations.messengers.discord.description': '連接 Discord 機器人以與 OpenChamber 聊天。', + 'settings.integrations.messengers.telegram.name': 'Telegram', + 'settings.integrations.messengers.telegram.description': '連接 Telegram 機器人以與 OpenChamber 聊天。', 'settings.integrations.thirdParty.title': '第三方整合', 'settings.integrations.thirdParty.info': '安裝並管理支援的 OpenCode 供應商外掛。設定會開啟供應商既有的驗證設定。', 'settings.integrations.thirdParty.actions.install': '安裝', diff --git a/packages/ui/src/lib/settings/metadata.ts b/packages/ui/src/lib/settings/metadata.ts index 8b03add0..eea1ba80 100644 --- a/packages/ui/src/lib/settings/metadata.ts +++ b/packages/ui/src/lib/settings/metadata.ts @@ -202,7 +202,7 @@ export const SETTINGS_PAGE_METADATA: readonly SettingsPageMeta[] = [ { slug: 'voice', title: 'Voice', group: 'general', kind: 'single', keywords: ['tts', 'speech', 'voice'], isAvailable: (ctx) => !ctx.isVSCode }, { slug: 'tunnel', title: 'External Tunnel', group: 'projects', kind: 'single', keywords: ['tunnel', 'external', 'cloudflare', 'qr', 'remote', 'mobile', 'share'], isAvailable: (ctx) => !ctx.isVSCode }, { slug: 'about', title: 'About', group: 'general', kind: 'single', keywords: ['about', 'version', 'updates', 'release', 'changelog'], isAvailable: (ctx) => ctx.isMobile && !ctx.isVSCode }, - { slug: 'integrations', title: 'Integrations', group: 'general', kind: 'single', keywords: ['integration', 'plugin', 'provider', 'oauth', 'claude', 'cursor', 'command code', 'connect'] }, + { slug: 'integrations', title: 'Integrations', group: 'general', kind: 'single', keywords: ['integration', 'plugin', 'provider', 'oauth', 'claude', 'cursor', 'command code', 'connect', 'discord', 'telegram', 'messenger'] }, ] as const; const LEGACY_SIDEBAR_SECTION_TO_SETTINGS_SLUG: Record = { diff --git a/packages/ui/src/lib/settings/search.test.ts b/packages/ui/src/lib/settings/search.test.ts index 33afe4b6..95491d6b 100644 --- a/packages/ui/src/lib/settings/search.test.ts +++ b/packages/ui/src/lib/settings/search.test.ts @@ -27,4 +27,15 @@ describe('settings search', () => { expect(results.some((result) => result.id === 'integrations.third-party.opencode-claude')).toBe(true); }); + + test('finds coming-soon messenger placeholders', () => { + const results = buildSettingsSearchResults({ + query: 'discord', + runtimeCtx, + t, + getPageTitle: (page) => page, + }); + + expect(results.some((result) => result.id === 'integrations.messengers.discord')).toBe(true); + }); }); diff --git a/packages/ui/src/lib/settings/search.ts b/packages/ui/src/lib/settings/search.ts index 10e24965..aca6dfd5 100644 --- a/packages/ui/src/lib/settings/search.ts +++ b/packages/ui/src/lib/settings/search.ts @@ -934,6 +934,26 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ isAvailable: (ctx) => ctx.isWeb && !ctx.isDesktop && !ctx.isVSCode, }, + { + id: 'integrations.messengers', + page: 'integrations', + titleKey: 'settings.integrations.messengers.title', + keywords: ['messenger', 'discord', 'telegram', 'bot', 'coming soon'], + }, + { + id: 'integrations.messengers.discord', + page: 'integrations', + titleKey: 'settings.integrations.messengers.discord.name', + descriptionKey: 'settings.integrations.messengers.discord.description', + keywords: ['discord', 'bot', 'messenger', 'coming soon'], + }, + { + id: 'integrations.messengers.telegram', + page: 'integrations', + titleKey: 'settings.integrations.messengers.telegram.name', + descriptionKey: 'settings.integrations.messengers.telegram.description', + keywords: ['telegram', 'bot', 'messenger', 'coming soon'], + }, { id: 'integrations.third-party', page: 'integrations',