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 ef4edbae..239e9b9d 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; +import i18n from 'i18next'; import { IChooseAdapterEntity, IPipelineEntity, @@ -13,6 +14,9 @@ import { UUID } from 'uuidjs'; import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent'; import { httpClient } from '@/app/infra/http/HttpClient'; import { Bot } from '@/app/infra/entities/api'; +import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs'; +import { ExternalLink } from 'lucide-react'; +import { Button } from '@/components/ui/button'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; @@ -528,6 +532,30 @@ export default function BotForm({ {adapterDescriptionList[currentAdapter]} )} + {currentAdapter && + (() => { + const docUrl = getAdapterDocUrl( + currentAdapter, + i18n.language, + ); + return docUrl ? ( + + + + {t('bots.viewAdapterDocs')} + + + ) : null; + })()} )} diff --git a/web/src/app/infra/entities/adapter-docs.ts b/web/src/app/infra/entities/adapter-docs.ts new file mode 100644 index 00000000..64ae9b1a --- /dev/null +++ b/web/src/app/infra/entities/adapter-docs.ts @@ -0,0 +1,43 @@ +/** + * Returns the documentation URL for a given adapter name, + * using link.langbot.app short links. + */ +export function getAdapterDocUrl( + adapterName: string, + locale: string, +): string | null { + // Map locale to doc language prefix + let lang: string; + if (locale.startsWith('zh')) { + lang = 'zh'; + } else if (locale.startsWith('ja')) { + lang = 'ja'; + } else { + lang = 'en'; + } + + // Only adapters with dedicated doc pages + const ADAPTER_DOC_SLUGS: Record = { + telegram: 'telegram', + discord: 'discord', + slack: 'slack', + line: 'line', + kook: 'kook', + lark: 'lark', + dingtalk: 'dingtalk', + aiocqhttp: 'aiocqhttp', + qqofficial: 'qqofficial', + wecom: 'wecom', + wecomcs: 'wecomcs', + wecombot: 'wecombot', + officialaccount: 'officialaccount', + wechatpad: 'wechatpad', + openclaw_weixin: 'openclaw_weixin', + satori: 'satori', + }; + + const slug = ADAPTER_DOC_SLUGS[adapterName]; + if (!slug) return null; + + return `https://link.langbot.app/${lang}/platforms/${slug}`; +} diff --git a/web/src/app/wizard/page.tsx b/web/src/app/wizard/page.tsx index 063c0a6c..c3b9831e 100644 --- a/web/src/app/wizard/page.tsx +++ b/web/src/app/wizard/page.tsx @@ -13,6 +13,7 @@ import { PartyPopper, Loader2, X, + ExternalLink, } from 'lucide-react'; import { httpClient } from '@/app/infra/http/HttpClient'; @@ -45,6 +46,8 @@ import { groupByCategory, getCategoryLabel, } from '@/app/infra/entities/adapter-categories'; +import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs'; +import i18n from 'i18next'; import { Button } from '@/components/ui/button'; import { @@ -798,6 +801,24 @@ function StepPlatform({ {extractI18nObject(adapter.description)} + {(() => { + const docUrl = getAdapterDocUrl( + adapter.name, + i18n.language, + ); + return docUrl ? ( + e.stopPropagation()} + > + + {t('bots.viewAdapterDocs')} + + ) : null; + })()} ))} @@ -867,11 +888,31 @@ function StepBotConfig({ {adapterConfigItems.length > 0 && ( - - {t('wizard.config.platformConfig', { - platform: adapterLabel, - })} - + + + {t('wizard.config.platformConfig', { + platform: adapterLabel, + })} + + {selectedAdapterName && + (() => { + const docUrl = getAdapterDocUrl( + selectedAdapterName, + i18n.language, + ); + return docUrl ? ( + + + {t('bots.viewAdapterDocs')} + + ) : null; + })()} +
{extractI18nObject(adapter.description)}