diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index 5491bec4..842ac821 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -124,6 +124,20 @@ export const en = { 'settings.adapters.saved': 'Saved', 'settings.adapters.saving': 'Saving...', 'settings.adapters.startHint': 'After saving, run adapter: cd adapters && bun run {platform}', + 'settings.adapters.pairing': 'Pairing', + 'settings.adapters.pairingDesc': 'Generate a pairing code and send it to the Bot via IM private chat to bind your identity.', + 'settings.adapters.generateCode': 'Generate Code', + 'settings.adapters.regenerateCode': 'Regenerate', + 'settings.adapters.codeExpired': 'Expired', + 'settings.adapters.codeExpiresIn': 'Expires in', + 'settings.adapters.minutes': 'min', + 'settings.adapters.pairingCodeHint': 'Send this code to the Bot in Feishu or Telegram private chat.', + 'settings.adapters.pairedUsers': 'Paired Users', + 'settings.adapters.noPairedUsers': 'No paired users yet', + 'settings.adapters.unbind': 'Unbind', + 'settings.adapters.unbindConfirm': 'Are you sure you want to unbind this user? They will need to re-pair to use the bot.', + 'settings.adapters.platform.telegram': 'Telegram', + 'settings.adapters.platform.feishu': 'Feishu', // Settings > General 'settings.general.languageTitle': 'Language', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 6d981179..295f08bf 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -126,6 +126,20 @@ export const zh: Record = { 'settings.adapters.saved': '已保存', 'settings.adapters.saving': '保存中...', 'settings.adapters.startHint': '保存后运行适配器:cd adapters && bun run {platform}', + 'settings.adapters.pairing': '配对管理', + 'settings.adapters.pairingDesc': '生成配对码后在 IM 私聊中发送给 Bot,完成身份绑定。', + 'settings.adapters.generateCode': '生成配对码', + 'settings.adapters.regenerateCode': '重新生成', + 'settings.adapters.codeExpired': '已过期', + 'settings.adapters.codeExpiresIn': '有效期剩余', + 'settings.adapters.minutes': '分钟', + 'settings.adapters.pairingCodeHint': '请在飞书或 Telegram 私聊中发送此配对码给 Bot。', + 'settings.adapters.pairedUsers': '已配对用户', + 'settings.adapters.noPairedUsers': '暂无已配对用户', + 'settings.adapters.unbind': '解绑', + 'settings.adapters.unbindConfirm': '确定要解绑该用户吗?解绑后需重新配对才能使用。', + 'settings.adapters.platform.telegram': 'Telegram', + 'settings.adapters.platform.feishu': '飞书', // Settings > General 'settings.general.languageTitle': '语言', diff --git a/desktop/src/pages/AdapterSettings.tsx b/desktop/src/pages/AdapterSettings.tsx index 8715a577..cfa15b62 100644 --- a/desktop/src/pages/AdapterSettings.tsx +++ b/desktop/src/pages/AdapterSettings.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useAdapterStore } from '../stores/adapterStore' import { useTranslation } from '../i18n' import { Input } from '../components/shared/Input' @@ -7,7 +7,7 @@ import { DirectoryPicker } from '../components/shared/DirectoryPicker' export function AdapterSettings() { const t = useTranslation() - const { config, isLoading, fetchConfig, updateConfig } = useAdapterStore() + const { config, isLoading, fetchConfig, updateConfig, generatePairingCode, removePairedUser } = useAdapterStore() // Server const [serverUrl, setServerUrl] = useState('') @@ -29,6 +29,10 @@ export function AdapterSettings() { const [saveStatus, setSaveStatus] = useState<'idle' | 'saved' | 'error'>('idle') const [saveError, setSaveError] = useState('') + // Pairing + const [pairingCode, setPairingCode] = useState(null) + const [isGenerating, setIsGenerating] = useState(false) + useEffect(() => { fetchConfig() }, []) @@ -94,6 +98,35 @@ export function AdapterSettings() { } } + const handleGenerateCode = useCallback(async () => { + setIsGenerating(true) + try { + const code = await generatePairingCode() + setPairingCode(code) + } catch (err) { + console.error('Failed to generate pairing code:', err) + } finally { + setIsGenerating(false) + } + }, [generatePairingCode]) + + const handleUnbind = useCallback(async (platform: 'telegram' | 'feishu', userId: string | number) => { + if (!confirm(t('settings.adapters.unbindConfirm'))) return + await removePairedUser(platform, userId) + await fetchConfig() + }, [removePairedUser, fetchConfig, t]) + + // Collect all paired users across platforms + const allPairedUsers = [ + ...(config.telegram?.pairedUsers ?? []).map((u) => ({ ...u, platform: 'telegram' as const })), + ...(config.feishu?.pairedUsers ?? []).map((u) => ({ ...u, platform: 'feishu' as const })), + ] + + // Check pairing expiry + 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 + if (isLoading) { return (
@@ -110,6 +143,75 @@ export function AdapterSettings() {

{t('settings.adapters.description')}

+ {/* Pairing */} +
+
+ link + {t('settings.adapters.pairing')} +
+
+

{t('settings.adapters.pairingDesc')}

+ + {/* Generate code */} +
+ + {pairingCode && ( +
+ + {pairingCode} + + + {t('settings.adapters.codeExpiresIn')} 60 {t('settings.adapters.minutes')} + +
+ )} + {!pairingCode && isPairingActive && ( + + {t('settings.adapters.codeExpiresIn')} {minutesLeft} {t('settings.adapters.minutes')} + + )} +
+ {pairingCode && ( +

{t('settings.adapters.pairingCodeHint')}

+ )} + + {/* Paired users list */} +
+

{t('settings.adapters.pairedUsers')}

+ {allPairedUsers.length === 0 ? ( +

{t('settings.adapters.noPairedUsers')}

+ ) : ( +
+ {allPairedUsers.map((user) => ( +
+
+ + {t(`settings.adapters.platform.${user.platform}`)} + + {user.displayName} + + {new Date(user.pairedAt).toLocaleDateString()} + +
+ +
+ ))} +
+ )} +
+
+
+ {/* Server URL */}