feat: add adapter documentation link button

Add 'View Docs' button that links to the corresponding adapter's
documentation page via link.langbot.app short links.

Appears in:
- Wizard adapter selection cards (Step 0)
- Wizard bot config card header (Step 1)
- Bot create/edit form (adapter config section)

Supports all 7 languages (en/zh-Hans/zh-Hant/ja/th/vi/es).
Doc links auto-resolve to the correct language based on UI locale.
This commit is contained in:
RockChinQ
2026-03-30 16:06:54 +08:00
parent 6bf6deaefd
commit 921d12f596
10 changed files with 124 additions and 5 deletions

View File

@@ -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]}
</FormDescription>
)}
{currentAdapter &&
(() => {
const docUrl = getAdapterDocUrl(
currentAdapter,
i18n.language,
);
return docUrl ? (
<Button
variant="link"
size="sm"
className="h-auto p-0 text-xs"
asChild
>
<a
href={docUrl}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="mr-1 h-3 w-3" />
{t('bots.viewAdapterDocs')}
</a>
</Button>
) : null;
})()}
<FormMessage />
</FormItem>
)}

View File

@@ -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<string, string> = {
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}`;
}

View File

@@ -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({
<p className="text-sm text-muted-foreground line-clamp-2">
{extractI18nObject(adapter.description)}
</p>
{(() => {
const docUrl = getAdapterDocUrl(
adapter.name,
i18n.language,
);
return docUrl ? (
<a
href={docUrl}
target="_blank"
rel="noopener noreferrer"
className="mt-2 inline-flex items-center text-xs text-primary hover:underline"
onClick={(e) => e.stopPropagation()}
>
<ExternalLink className="mr-1 h-3 w-3" />
{t('bots.viewAdapterDocs')}
</a>
) : null;
})()}
</CardContent>
</Card>
))}
@@ -867,11 +888,31 @@ function StepBotConfig({
{adapterConfigItems.length > 0 && (
<Card>
<CardHeader className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2">
<CardTitle className="text-base">
{t('wizard.config.platformConfig', {
platform: adapterLabel,
})}
</CardTitle>
<div className="flex items-center gap-2">
<CardTitle className="text-base">
{t('wizard.config.platformConfig', {
platform: adapterLabel,
})}
</CardTitle>
{selectedAdapterName &&
(() => {
const docUrl = getAdapterDocUrl(
selectedAdapterName,
i18n.language,
);
return docUrl ? (
<a
href={docUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-xs text-primary hover:underline"
>
<ExternalLink className="mr-1 h-3 w-3" />
{t('bots.viewAdapterDocs')}
</a>
) : null;
})()}
</div>
<Button
size="sm"
onClick={onSaveBot}

View File

@@ -288,6 +288,7 @@ const enUS = {
platformAdapter: 'Platform/Adapter Selection',
selectAdapter: 'Select Adapter',
adapterConfig: 'Adapter Configuration',
viewAdapterDocs: 'View Docs',
bindPipeline: 'Bind Pipeline',
selectPipeline: 'Select Pipeline',
selectBot: 'Select Bot',

View File

@@ -297,6 +297,7 @@ const esES = {
platformAdapter: 'Selección de plataforma/adaptador',
selectAdapter: 'Seleccionar adaptador',
adapterConfig: 'Configuración del adaptador',
viewAdapterDocs: 'Ver documentación',
bindPipeline: 'Vincular Pipeline',
selectPipeline: 'Seleccionar Pipeline',
selectBot: 'Seleccionar Bot',

View File

@@ -293,6 +293,7 @@
platformAdapter: 'プラットフォーム/アダプター選択',
selectAdapter: 'アダプターを選択',
adapterConfig: 'アダプター設定',
viewAdapterDocs: 'ドキュメントを見る',
bindPipeline: 'パイプラインを紐付け',
selectPipeline: 'パイプラインを選択',
selectBot: 'ボットを選択してください',

View File

@@ -283,6 +283,7 @@ const thTH = {
platformAdapter: 'การเลือกแพลตฟอร์ม/อะแดปเตอร์',
selectAdapter: 'เลือกอะแดปเตอร์',
adapterConfig: 'การกำหนดค่าอะแดปเตอร์',
viewAdapterDocs: 'ดูเอกสาร',
bindPipeline: 'ผูก Pipeline',
selectPipeline: 'เลือก Pipeline',
selectBot: 'เลือก Bot',

View File

@@ -292,6 +292,7 @@ const viVN = {
platformAdapter: 'Nền tảng/Lựa chọn Adapter',
selectAdapter: 'Chọn Adapter',
adapterConfig: 'Cấu hình Adapter',
viewAdapterDocs: 'Xem tài liệu',
bindPipeline: 'Liên kết Pipeline',
selectPipeline: 'Chọn Pipeline',
selectBot: 'Chọn Bot',

View File

@@ -276,6 +276,7 @@ const zhHans = {
platformAdapter: '平台/适配器选择',
selectAdapter: '选择适配器',
adapterConfig: '适配器配置',
viewAdapterDocs: '查看文档',
bindPipeline: '绑定流水线',
selectPipeline: '选择流水线',
selectBot: '请选择机器人',

View File

@@ -275,6 +275,7 @@ const zhHant = {
platformAdapter: '平台/適配器選擇',
selectAdapter: '選擇適配器',
adapterConfig: '適配器設定',
viewAdapterDocs: '查看文檔',
bindPipeline: '綁定流程線',
selectPipeline: '選擇流程線',
selectBot: '請選擇機器人',