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:
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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}} が処理",
|
||||
|
||||
@@ -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}} 处理",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user