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}