fix(feishu): feishu connector name validate (#797)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
Haze
2026-04-08 19:16:15 +08:00
committed by GitHub
Unverified
parent c1e165d48d
commit d03902dd4d
13 changed files with 521 additions and 17 deletions

View File

@@ -32,7 +32,11 @@ import {
type ChannelMeta,
type ChannelConfigField,
} from '@/types/channel';
import { buildQrChannelEventName, usesPluginManagedQrAccounts } from '@/lib/channel-alias';
import {
buildQrChannelEventName,
isCanonicalOpenClawAccountId,
usesPluginManagedQrAccounts,
} from '@/lib/channel-alias';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import telegramIcon from '@/assets/channels/telegram.svg';
@@ -82,6 +86,7 @@ export function ChannelConfigModal({
const [configValues, setConfigValues] = useState<Record<string, string>>({});
const [channelName, setChannelName] = useState('');
const [accountIdInput, setAccountIdInput] = useState(accountId || '');
const [accountIdError, setAccountIdError] = useState<string | null>(null);
const [connecting, setConnecting] = useState(false);
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({});
const [qrCode, setQrCode] = useState<string | null>(null);
@@ -115,6 +120,7 @@ export function ChannelConfigModal({
useEffect(() => {
setAccountIdInput(accountId || '');
setAccountIdError(null);
}, [accountId]);
useEffect(() => {
@@ -125,6 +131,7 @@ export function ChannelConfigModal({
setValidationResult(null);
setQrCode(null);
setConnecting(false);
setAccountIdError(null);
return;
}
@@ -352,16 +359,28 @@ export function ChannelConfigModal({
if (showAccountIdEditor) {
const nextAccountId = accountIdInput.trim();
if (!nextAccountId) {
toast.error(t('account.invalidId'));
const message = t('account.invalidId');
setAccountIdError(message);
toast.error(message);
setConnecting(false);
return;
}
if (!isCanonicalOpenClawAccountId(nextAccountId)) {
const message = t('account.invalidCanonicalId');
setAccountIdError(message);
toast.error(message);
setConnecting(false);
return;
}
const duplicateExists = existingAccountIds.some((id) => id === nextAccountId && id !== (accountId || '').trim());
if (duplicateExists) {
toast.error(t('account.accountIdExists', { accountId: nextAccountId }));
const message = t('account.accountIdExists', { accountId: nextAccountId });
setAccountIdError(message);
toast.error(message);
setConnecting(false);
return;
}
setAccountIdError(null);
}
if (meta.connectionType === 'qr') {
@@ -643,11 +662,20 @@ export function ChannelConfigModal({
<Input
id="account-id"
value={accountIdInput}
onChange={(event) => setAccountIdInput(event.target.value)}
onChange={(event) => {
setAccountIdInput(event.target.value);
if (accountIdError) {
setAccountIdError(null);
}
}}
placeholder={t('account.customIdPlaceholder')}
className={inputClasses}
className={cn(inputClasses, accountIdError && 'border-destructive/50 focus-visible:ring-destructive/30')}
/>
<p className="text-[12px] text-muted-foreground">{t('account.customIdHint')}</p>
{accountIdError ? (
<p className="text-[12px] text-destructive">{accountIdError}</p>
) : (
<p className="text-[12px] text-muted-foreground">{t('account.customIdHint')}</p>
)}
</div>
)}

View File

@@ -47,8 +47,9 @@
"mainAccount": "Primary Account",
"customIdLabel": "Account ID",
"customIdPlaceholder": "e.g. feishu-sales-bot",
"customIdHint": "Use a custom account ID to distinguish multiple accounts under one channel.",
"customIdHint": "Use a lowercase account ID (letters, numbers, hyphen, underscore) to distinguish multiple accounts under one channel.",
"invalidId": "Account ID cannot be empty",
"invalidCanonicalId": "Account ID must use lowercase letters, numbers, hyphens, or underscores, start with a letter/number, and be at most 64 characters.",
"idLabel": "ID: {{id}}",
"boundTo": "Bound to: {{agent}}",
"handledBy": "Handled by {{agent}}",

View File

@@ -47,8 +47,9 @@
"mainAccount": "メインアカウント",
"customIdLabel": "アカウント ID",
"customIdPlaceholder": "例: feishu-sales-bot",
"customIdHint": "同じチャンネル内の複数アカウントを区別するため、任意の ID を設定できます。",
"customIdHint": "同じチャンネル内の複数アカウントを区別するため、英小文字・数字・ハイフン・アンダースコアのみの ID を設定してください。",
"invalidId": "アカウント ID は空にできません",
"invalidCanonicalId": "アカウント ID は英小文字・数字・ハイフン・アンダースコアのみ使用でき、先頭は英小文字または数字、最大 64 文字です。",
"idLabel": "ID: {{id}}",
"boundTo": "割り当て先: {{agent}}",
"handledBy": "{{agent}} が処理",

View File

@@ -47,8 +47,9 @@
"mainAccount": "主账号",
"customIdLabel": "账号 ID",
"customIdPlaceholder": "例如feishu-sales-bot",
"customIdHint": "可自定义账号 ID,用于区分同一频道下的多个账号。",
"customIdHint": "使用小写账号 ID(字母、数字、连字符、下划线)来区分同一频道下的多个账号。",
"invalidId": "账号 ID 不能为空",
"invalidCanonicalId": "账号 ID 仅支持小写字母、数字、连字符和下划线;必须以字母或数字开头,且最长 64 个字符。",
"idLabel": "ID: {{id}}",
"boundTo": "绑定对象:{{agent}}",
"handledBy": "由 {{agent}} 处理",

View File

@@ -48,3 +48,9 @@ export function normalizeOpenClawAccountId(value: string | null | undefined, fal
}
return normalized;
}
export function isCanonicalOpenClawAccountId(value: string | null | undefined): boolean {
const trimmed = (value ?? '').trim();
if (!trimmed) return false;
return normalizeOpenClawAccountId(trimmed, '') === trimmed;
}