fix: reduce Feishu IM setup dead ends

Feishu onboarding previously expected users to find the template bot flow in docs before the desktop form made sense. Surface the documented OpenClaw creation link and the two required setup steps directly in the unconfigured Feishu settings state, while hiding the prompt once saved credentials exist.

Constraint: The documented one-click template URL is the source of truth for bot creation.
Rejected: Add a full wizard | this flow only needs a short external creation link plus credential fields.
Confidence: high
Scope-risk: narrow
Directive: Keep this prompt short; detailed Feishu menu setup belongs in docs, not the settings form.
Tested: cd desktop && bunx vitest run src/pages/AdapterSettings.test.tsx src/i18n/index.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
Tested: bun run check:desktop
Not-tested: Live Feishu developer console account flow.
This commit is contained in:
程序员阿江(Relakkes) 2026-05-19 00:08:17 +08:00
parent d7315df18b
commit 51e67f20ff
4 changed files with 93 additions and 0 deletions

View File

@ -376,6 +376,11 @@ export const en = {
'settings.adapters.appIdPlaceholder': 'e.g. cli_xxx',
'settings.adapters.appSecret': 'App Secret',
'settings.adapters.appSecretPlaceholder': 'From Feishu Open Platform',
'settings.adapters.feishuCreateBotTitle': 'Need a Feishu bot?',
'settings.adapters.feishuCreateBotDesc': 'Use the documented OpenClaw template to create a Feishu bot with the required permissions already configured. Then paste the App ID and App Secret below.',
'settings.adapters.feishuCreateBotAction': 'Create Feishu bot',
'settings.adapters.feishuCreateBotStepCreate': 'Create the bot from the template.',
'settings.adapters.feishuCreateBotStepFill': 'Copy its App ID and App Secret, then fill them in here.',
'settings.adapters.encryptKey': 'Encrypt Key',
'settings.adapters.encryptKeyPlaceholder': 'Optional',
'settings.adapters.verificationToken': 'Verification Token',

View File

@ -378,6 +378,11 @@ export const zh: Record<TranslationKey, string> = {
'settings.adapters.appIdPlaceholder': '如 cli_xxx',
'settings.adapters.appSecret': 'App Secret',
'settings.adapters.appSecretPlaceholder': '从飞书开放平台获取',
'settings.adapters.feishuCreateBotTitle': '还没有飞书机器人?',
'settings.adapters.feishuCreateBotDesc': '使用文档中的官方 OpenClaw 模板,一键创建已预配权限的飞书机器人。创建后把 App ID 和 App Secret 填到下方。',
'settings.adapters.feishuCreateBotAction': '一键创建飞书机器人',
'settings.adapters.feishuCreateBotStepCreate': '点击模板创建机器人。',
'settings.adapters.feishuCreateBotStepFill': '复制 App ID 和 App Secret回到这里填写。',
'settings.adapters.encryptKey': 'Encrypt Key',
'settings.adapters.encryptKeyPlaceholder': '可选',
'settings.adapters.verificationToken': 'Verification Token',

View File

@ -0,0 +1,54 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { AdapterSettings } from './AdapterSettings'
import { useAdapterStore } from '../stores/adapterStore'
import { useSettingsStore } from '../stores/settingsStore'
import type { AdapterFileConfig } from '../types/adapter'
const FEISHU_CREATE_BOT_URL = 'https://open.feishu.cn/page/openclaw?form=multiAgent'
function renderAdapterSettings(config: AdapterFileConfig) {
useSettingsStore.setState({ locale: 'en' })
useAdapterStore.setState({
config,
isLoading: false,
fetchConfig: vi.fn(async () => {}),
} as Partial<ReturnType<typeof useAdapterStore.getState>>)
render(<AdapterSettings />)
}
afterEach(() => {
cleanup()
useAdapterStore.setState(useAdapterStore.getInitialState(), true)
useSettingsStore.setState(useSettingsStore.getInitialState(), true)
})
describe('AdapterSettings Feishu onboarding', () => {
it('shows the documented one-click Feishu bot link before credentials are configured', () => {
renderAdapterSettings({})
expect(screen.getByText('Need a Feishu bot?')).toBeInTheDocument()
expect(screen.getByText(/OpenClaw template/)).toBeInTheDocument()
expect(screen.getByText('1. Create the bot from the template.')).toBeInTheDocument()
expect(screen.getByText('2. Copy its App ID and App Secret, then fill them in here.')).toBeInTheDocument()
expect(screen.getByRole('link', { name: /create feishu bot/i })).toHaveAttribute(
'href',
FEISHU_CREATE_BOT_URL,
)
})
it('hides the one-click Feishu bot prompt once saved credentials exist', () => {
renderAdapterSettings({
feishu: {
appId: 'cli_existing',
appSecret: '****cret',
},
})
expect(screen.queryByRole('link', { name: /create feishu bot/i })).not.toBeInTheDocument()
expect(screen.queryByText('Need a Feishu bot?')).not.toBeInTheDocument()
})
})

View File

@ -10,6 +10,8 @@ import QRCode from 'qrcode'
type ImTab = 'feishu' | 'wechat' | 'dingtalk' | 'telegram'
type ImPlatform = 'telegram' | 'feishu' | 'wechat' | 'dingtalk'
const FEISHU_CREATE_BOT_URL = 'https://open.feishu.cn/page/openclaw?form=multiAgent'
export function AdapterSettings() {
const t = useTranslation()
const {
@ -375,6 +377,7 @@ export function AdapterSettings() {
const pairingExpiry = config.pairing?.expiresAt
const isPairingActive = pairingExpiry ? Date.now() < pairingExpiry : false
const minutesLeft = pairingExpiry ? Math.max(0, Math.ceil((pairingExpiry - Date.now()) / 60000)) : 0
const hasSavedFeishuCredentials = Boolean(config.feishu?.appId && config.feishu?.appSecret)
if (isLoading) {
return (
@ -505,6 +508,32 @@ export function AdapterSettings() {
{activeIm === 'feishu' && (
<div className="p-4 space-y-4">
{!hasSavedFeishuCredentials && (
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface)] p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="flex min-w-0 gap-3">
<span className="material-symbols-outlined mt-0.5 text-[20px] text-[var(--color-brand)]">smart_toy</span>
<div className="min-w-0">
<h4 className="text-sm font-semibold text-[var(--color-text-primary)]">{t('settings.adapters.feishuCreateBotTitle')}</h4>
<p className="mt-1 text-xs leading-5 text-[var(--color-text-tertiary)]">{t('settings.adapters.feishuCreateBotDesc')}</p>
<ol className="mt-2 space-y-1 text-xs leading-5 text-[var(--color-text-secondary)]">
<li>1. {t('settings.adapters.feishuCreateBotStepCreate')}</li>
<li>2. {t('settings.adapters.feishuCreateBotStepFill')}</li>
</ol>
</div>
</div>
<a
href={FEISHU_CREATE_BOT_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex h-9 shrink-0 items-center justify-center gap-1.5 rounded-[var(--radius-md)] bg-[image:var(--gradient-btn-primary)] px-3 text-xs font-medium text-[var(--color-btn-primary-fg)] shadow-[var(--shadow-button-primary)] transition-colors hover:bg-[image:var(--gradient-btn-primary-hover)] hover:brightness-105 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-surface)]"
>
{t('settings.adapters.feishuCreateBotAction')}
<span className="material-symbols-outlined text-[14px]">open_in_new</span>
</a>
</div>
</div>
)}
<div className="grid grid-cols-2 gap-4">
<Input
label={t('settings.adapters.appId')}