From ce31fc81b85872459702d727fc8286ac7dcf6b4e Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Fri, 26 Jun 2026 19:10:07 +0800 Subject: [PATCH] feat(adapters): mark EBA-superseded adapters as legacy and collapse them The 12 old adapters that now have an EBA replacement are tagged `spec.legacy: true` in their source manifests. Principle: don't delete, de-emphasize. - sources/*.yaml (aiocqhttp, dingtalk, discord, kook, lark, officialaccount, qqofficial, slack, telegram, wecom, wecombot, wecomcs): add spec.legacy: true - Adapter / IChooseAdapterEntity types: add optional legacy flag - BotForm adapter Select: split legacy adapters into a collapsed, grayscale group at the bottom with an explanatory hint; auto-expand when the bot already uses a legacy adapter - Wizard platform picker: same collapsed legacy section - i18n: legacyAdapters / legacyAdaptersHint (zh-Hans, en-US) Co-Authored-By: Claude Opus 4.8 --- .../pkg/platform/sources/aiocqhttp.yaml | 1 + .../pkg/platform/sources/dingtalk.yaml | 1 + src/langbot/pkg/platform/sources/discord.yaml | 1 + src/langbot/pkg/platform/sources/kook.yaml | 1 + src/langbot/pkg/platform/sources/lark.yaml | 1 + .../pkg/platform/sources/officialaccount.yaml | 1 + .../pkg/platform/sources/qqofficial.yaml | 1 + src/langbot/pkg/platform/sources/slack.yaml | 1 + .../pkg/platform/sources/telegram.yaml | 1 + src/langbot/pkg/platform/sources/wecom.yaml | 1 + .../pkg/platform/sources/wecombot.yaml | 1 + src/langbot/pkg/platform/sources/wecomcs.yaml | 1 + .../home/bots/components/bot-form/BotForm.tsx | 89 ++++++++++++++++++- .../bots/components/bot-form/ChooseEntity.ts | 1 + web/src/app/infra/entities/api/index.ts | 1 + web/src/app/wizard/page.tsx | 76 +++++++++++++++- web/src/i18n/locales/en-US.ts | 3 + web/src/i18n/locales/zh-Hans.ts | 3 + 18 files changed, 179 insertions(+), 6 deletions(-) diff --git a/src/langbot/pkg/platform/sources/aiocqhttp.yaml b/src/langbot/pkg/platform/sources/aiocqhttp.yaml index b93a25243..18b233750 100644 --- a/src/langbot/pkg/platform/sources/aiocqhttp.yaml +++ b/src/langbot/pkg/platform/sources/aiocqhttp.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: OneBot v11 適配器,用於接入 QQ 機器人協定端,請查看文件了解使用方式 icon: onebot.png spec: + legacy: true categories: - protocol help_links: diff --git a/src/langbot/pkg/platform/sources/dingtalk.yaml b/src/langbot/pkg/platform/sources/dingtalk.yaml index c7c25e673..32412ef2d 100644 --- a/src/langbot/pkg/platform/sources/dingtalk.yaml +++ b/src/langbot/pkg/platform/sources/dingtalk.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: 釘釘適配器,請查看文件了解使用方式 icon: dingtalk.svg spec: + legacy: true categories: - china help_links: diff --git a/src/langbot/pkg/platform/sources/discord.yaml b/src/langbot/pkg/platform/sources/discord.yaml index 28149cbda..c781110bd 100644 --- a/src/langbot/pkg/platform/sources/discord.yaml +++ b/src/langbot/pkg/platform/sources/discord.yaml @@ -20,6 +20,7 @@ metadata: es_ES: Adaptador de Discord, requiere un entorno de red con acceso al servidor de Discord icon: discord.svg spec: + legacy: true categories: - popular - global diff --git a/src/langbot/pkg/platform/sources/kook.yaml b/src/langbot/pkg/platform/sources/kook.yaml index c63d35eed..e485190de 100644 --- a/src/langbot/pkg/platform/sources/kook.yaml +++ b/src/langbot/pkg/platform/sources/kook.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: KOOK 適配器(原開黑啦),支援頻道訊息和私聊訊息 icon: kook.png spec: + legacy: true categories: - china help_links: diff --git a/src/langbot/pkg/platform/sources/lark.yaml b/src/langbot/pkg/platform/sources/lark.yaml index 94509c470..acde1da56 100644 --- a/src/langbot/pkg/platform/sources/lark.yaml +++ b/src/langbot/pkg/platform/sources/lark.yaml @@ -14,6 +14,7 @@ metadata: ja_JP: Lark アダプター、長期接続およびWebhookモードの両方をサポートしています。使用方法の詳細については、ドキュメントを参照してください。 icon: lark.svg spec: + legacy: true categories: - popular - china diff --git a/src/langbot/pkg/platform/sources/officialaccount.yaml b/src/langbot/pkg/platform/sources/officialaccount.yaml index d09538028..19b7c8621 100644 --- a/src/langbot/pkg/platform/sources/officialaccount.yaml +++ b/src/langbot/pkg/platform/sources/officialaccount.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: 微信公眾號適配器,需要公網地址以接收訊息推送,請查看文件了解使用方式 icon: officialaccount.png spec: + legacy: true categories: - china help_links: diff --git a/src/langbot/pkg/platform/sources/qqofficial.yaml b/src/langbot/pkg/platform/sources/qqofficial.yaml index d66a770bf..1ded7e218 100644 --- a/src/langbot/pkg/platform/sources/qqofficial.yaml +++ b/src/langbot/pkg/platform/sources/qqofficial.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: QQ 官方 API,支援 Webhook 和 WebSocket 兩種連線模式 icon: qqofficial.svg spec: + legacy: true categories: - china help_links: diff --git a/src/langbot/pkg/platform/sources/slack.yaml b/src/langbot/pkg/platform/sources/slack.yaml index f1a2e740a..2827276a2 100644 --- a/src/langbot/pkg/platform/sources/slack.yaml +++ b/src/langbot/pkg/platform/sources/slack.yaml @@ -20,6 +20,7 @@ metadata: es_ES: Adaptador de Slack, requiere una dirección pública para recibir notificaciones de mensajes de Slack, consulte la documentación para obtener instrucciones de uso icon: slack.png spec: + legacy: true categories: - popular - global diff --git a/src/langbot/pkg/platform/sources/telegram.yaml b/src/langbot/pkg/platform/sources/telegram.yaml index f652ae420..c3f38d7e3 100644 --- a/src/langbot/pkg/platform/sources/telegram.yaml +++ b/src/langbot/pkg/platform/sources/telegram.yaml @@ -20,6 +20,7 @@ metadata: es_ES: Adaptador de Telegram, consulte la documentación para obtener instrucciones de uso icon: telegram.svg spec: + legacy: true categories: - popular - global diff --git a/src/langbot/pkg/platform/sources/wecom.yaml b/src/langbot/pkg/platform/sources/wecom.yaml index 509fa4e0f..64f4c00e4 100644 --- a/src/langbot/pkg/platform/sources/wecom.yaml +++ b/src/langbot/pkg/platform/sources/wecom.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: 企業微信內部機器人,請查看文件了解使用方式 icon: wecom.png spec: + legacy: true categories: - popular - china diff --git a/src/langbot/pkg/platform/sources/wecombot.yaml b/src/langbot/pkg/platform/sources/wecombot.yaml index 7e95402ee..0dc1f2cd0 100644 --- a/src/langbot/pkg/platform/sources/wecombot.yaml +++ b/src/langbot/pkg/platform/sources/wecombot.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: 企業微信智慧機器人,支援長連線和 Webhook 兩種接入方式,請查看文件了解使用方式 icon: wecombot.png spec: + legacy: true categories: - china help_links: diff --git a/src/langbot/pkg/platform/sources/wecomcs.yaml b/src/langbot/pkg/platform/sources/wecomcs.yaml index 521e7bb0e..5f6fb26ee 100644 --- a/src/langbot/pkg/platform/sources/wecomcs.yaml +++ b/src/langbot/pkg/platform/sources/wecomcs.yaml @@ -12,6 +12,7 @@ metadata: zh_Hant: 企業微信對外客服機器人,需要公網地址以接收訊息推送,請查看文件了解使用方式 icon: wecom.png spec: + legacy: true categories: - china help_links: diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index eb7bea89b..cd864bd52 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -13,7 +13,7 @@ import { httpClient } from '@/app/infra/http/HttpClient'; import { systemInfo } from '@/app/infra/http'; import { Agent, Bot } from '@/app/infra/entities/api'; import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs'; -import { ExternalLink } from 'lucide-react'; +import { ExternalLink, ChevronDown, ChevronRight } from 'lucide-react'; import EventBindingsEditor from './EventBindingsEditor'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -138,11 +138,35 @@ export default function BotForm({ const currentAdapter = form.watch('adapter'); const currentAdapterConfig = form.watch('adapter_config'); - // Group adapters by category for the Select dropdown - const groupedAdapters = useMemo( - () => groupByCategory(adapterNameList), + // Group adapters by category for the Select dropdown. Legacy adapters + // (those superseded by an EBA implementation) are split out and shown in a + // collapsed group at the bottom so they're de-emphasized but still usable. + const activeAdapters = useMemo( + () => adapterNameList.filter((a) => !a.legacy), [adapterNameList], ); + const legacyAdapters = useMemo( + () => adapterNameList.filter((a) => a.legacy), + [adapterNameList], + ); + const groupedAdapters = useMemo( + () => groupByCategory(activeAdapters), + [activeAdapters], + ); + + // Whether the collapsed legacy adapter group is expanded in the Select. + const [showLegacyAdapters, setShowLegacyAdapters] = useState(false); + + // Auto-expand the legacy group when the selected adapter is itself legacy, + // so editing an existing bot on a legacy adapter still reveals the choice. + useEffect(() => { + if ( + currentAdapter && + legacyAdapters.some((a) => a.value === currentAdapter) + ) { + setShowLegacyAdapters(true); + } + }, [currentAdapter, legacyAdapters]); // Notify parent when dirty state changes const { isDirty } = form.formState; @@ -207,6 +231,7 @@ export default function BotForm({ label: extractI18nObject(item.label), value: item.name, categories: item.spec.categories, + legacy: item.spec.legacy, }; }), ); @@ -514,6 +539,62 @@ export default function BotForm({ ))} ))} + {legacyAdapters.length > 0 && ( + <> +
{ + e.preventDefault(); + e.stopPropagation(); + setShowLegacyAdapters((v) => !v); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setShowLegacyAdapters((v) => !v); + } + }} + className="flex cursor-pointer items-center gap-1 px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground border-t mt-1 pt-2" + > + {showLegacyAdapters ? ( + + ) : ( + + )} + {t('bots.legacyAdapters')} + + {legacyAdapters.length} + +
+ {showLegacyAdapters && ( + <> +

+ {t('bots.legacyAdaptersHint')} +

+ + {legacyAdapters.map((item) => ( + +
+ + {item.label} +
+
+ ))} +
+ + )} + + )} {currentAdapter && diff --git a/web/src/app/home/bots/components/bot-form/ChooseEntity.ts b/web/src/app/home/bots/components/bot-form/ChooseEntity.ts index b7ba86968..c460f06b4 100644 --- a/web/src/app/home/bots/components/bot-form/ChooseEntity.ts +++ b/web/src/app/home/bots/components/bot-form/ChooseEntity.ts @@ -2,6 +2,7 @@ export interface IChooseAdapterEntity { label: string; value: string; categories?: string[]; + legacy?: boolean; } export interface IPipelineEntity { diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index 7a4ac4304..a2212b0d7 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -205,6 +205,7 @@ export interface Adapter { icon?: string; spec: { categories?: string[]; + legacy?: boolean; help_links?: Record; supported_events?: string[]; supported_apis?: string[]; diff --git a/web/src/app/wizard/page.tsx b/web/src/app/wizard/page.tsx index 955e008b4..a6655e403 100644 --- a/web/src/app/wizard/page.tsx +++ b/web/src/app/wizard/page.tsx @@ -7,6 +7,8 @@ import { ArrowLeft, ArrowRight, Check, + ChevronDown, + ChevronRight, Sparkles, PartyPopper, Loader2, @@ -765,14 +767,24 @@ function StepPlatform({ onSelect: (name: string) => void; }) { const { t } = useTranslation(); + const [showLegacy, setShowLegacy] = useState(false); + + const activeAdapters = useMemo( + () => adapters.filter((a) => !a.spec.legacy), + [adapters], + ); + const legacyAdapters = useMemo( + () => adapters.filter((a) => a.spec.legacy), + [adapters], + ); const groupedAdapters = useMemo(() => { - const withCategories = adapters.map((a) => ({ + const withCategories = activeAdapters.map((a) => ({ ...a, categories: a.spec.categories, })); return groupByCategory(withCategories); - }, [adapters]); + }, [activeAdapters]); return (
@@ -848,6 +860,66 @@ function StepPlatform({
))} + {legacyAdapters.length > 0 && ( +
+ + {showLegacy && ( + <> +

+ {t('bots.legacyAdaptersHint')} +

+
+ {legacyAdapters.map((adapter) => ( + onSelect(adapter.name)} + > + + +
+ + {extractI18nObject(adapter.label)} + +
+ {selected === adapter.name && ( +
+
+ +
+
+ )} +
+
+ ))} +
+ + )} +
+ )} ); } diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 99dbccf31..2438e5907 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -339,6 +339,9 @@ const enUS = { deleteConfirmation: 'Are you sure you want to delete this bot?', platformAdapter: 'Platform/Adapter Selection', selectAdapter: 'Select Adapter', + legacyAdapters: 'Legacy adapters', + legacyAdaptersHint: + 'These adapters are superseded by their newer (EBA) versions. They are kept only for existing configurations and are not recommended for new bots.', adapterConfig: 'Adapter Configuration', viewAdapterDocs: 'View Docs', bindPipeline: 'Bind Pipeline', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index cbd3e8575..0429ba17b 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -324,6 +324,9 @@ const zhHans = { deleteConfirmation: '你确定要删除这个机器人吗?', platformAdapter: '平台/适配器选择', selectAdapter: '选择适配器', + legacyAdapters: '旧版适配器', + legacyAdaptersHint: + '这些适配器已被新版(EBA 架构)取代,仅为兼容存量配置保留,不建议新建机器人时使用。', adapterConfig: '适配器配置', viewAdapterDocs: '查看文档', bindPipeline: '绑定流水线',