'use client'; import React, { useEffect, useState } from 'react'; import Link from 'next/link'; import { CheckCircle2, XCircle, AlertCircle, RefreshCw, Server, Cpu, Radio, Key, Loader2, Settings2, ScrollText, QrCode, PlugZap, } from 'lucide-react'; import { getChannelConfig, getChannelConnectorSession, getStatus, listChannelConnectors, listChannelEvents, restartRuntime, startChannelConnectorSession, updateAgentConfig, updateChannelConfig, updateProviderConfig, } from '@/lib/api'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import type { ChannelConfigDetail, ChannelConnectorDescriptor, ChannelEventRecord, ChannelStatus, ConnectorSessionResponse, ProviderStatus, SystemStatus, } from '@/types'; import { AppLocale, pickAppText } from '@/lib/i18n/core'; import { useAppI18n } from '@/lib/i18n/provider'; type ProviderFormState = { enabled: boolean; model: string; apiKey: string; apiBase: string; requestTimeoutSeconds: string; }; type AgentFormState = { maxTokens: string; temperature: string; maxToolIterations: string; }; type ChannelFormState = { enabled: boolean; kind: string; mode: string; accountId: string; displayName: string; botToken: string; appId: string; appSecret: string; clientSecret: string; token: string; domain: string; connectionMode: string; botUsername: string; botOpenId: string; webhookUrl: string; webhookSecret: string; requireMentionInGroups: boolean; allowFrom: string; groupAllowFrom: string; dmPolicy: string; groupPolicy: string; markdownSupport: boolean; baseUrl: string; cdnBaseUrl: string; maxMessageChars: string; textBatchDelaySeconds: string; }; const EMPTY_CHANNEL_FORM: ChannelFormState = { enabled: false, kind: 'telegram', mode: 'polling', accountId: '', displayName: '', botToken: '', appId: '', appSecret: '', clientSecret: '', token: '', domain: 'feishu', connectionMode: 'websocket', botUsername: '', botOpenId: '', webhookUrl: '', webhookSecret: '', requireMentionInGroups: true, allowFrom: '', groupAllowFrom: '', dmPolicy: 'open', groupPolicy: 'allowlist', markdownSupport: false, baseUrl: '', cdnBaseUrl: '', maxMessageChars: '', textBatchDelaySeconds: '', }; const CONFIGURABLE_CHANNEL_KINDS = new Set(['telegram', 'feishu', 'qqbot', 'weixin']); const SESSION_CONNECTOR_KINDS = new Set(['weixin', 'feishu']); type ConnectorWizardForm = { kind: string; displayName: string; domain: string; mode: 'create' | 'link'; appId: string; appSecret: string; verificationToken: string; }; const EMPTY_CONNECTOR_WIZARD: ConnectorWizardForm = { kind: '', displayName: '', domain: 'feishu', mode: 'create', appId: '', appSecret: '', verificationToken: '', }; export default function StatusPage() { const { locale } = useAppI18n(); const [status, setStatus] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [selectedProvider, setSelectedProvider] = useState(null); const [providerForm, setProviderForm] = useState(() => ({ enabled: false, model: '', apiKey: '', apiBase: '', requestTimeoutSeconds: '', })); const [savingProvider, setSavingProvider] = useState(false); const [providerError, setProviderError] = useState(null); const [agentForm, setAgentForm] = useState(() => ({ maxTokens: '', temperature: '0.2', maxToolIterations: '30', })); const [savingAgent, setSavingAgent] = useState(false); const [agentError, setAgentError] = useState(null); const [selectedChannel, setSelectedChannel] = useState(null); const [channelConfig, setChannelConfig] = useState(null); const [channelForm, setChannelForm] = useState(() => ({ ...EMPTY_CHANNEL_FORM })); const [channelEvents, setChannelEvents] = useState([]); const [loadingChannelConfig, setLoadingChannelConfig] = useState(false); const [loadingChannelEvents, setLoadingChannelEvents] = useState(false); const [savingChannel, setSavingChannel] = useState(false); const [channelError, setChannelError] = useState(null); const [channelRestartRequired, setChannelRestartRequired] = useState(false); const [restartOpen, setRestartOpen] = useState(false); const [restarting, setRestarting] = useState(false); const [restartError, setRestartError] = useState(null); const [connectors, setConnectors] = useState([]); const [loadingConnectors, setLoadingConnectors] = useState(false); const [connectorDialogOpen, setConnectorDialogOpen] = useState(false); const [connectorForm, setConnectorForm] = useState(() => ({ ...EMPTY_CONNECTOR_WIZARD })); const [connectorSession, setConnectorSession] = useState(null); const [startingConnector, setStartingConnector] = useState(false); const [pollingConnector, setPollingConnector] = useState(false); const [connectorError, setConnectorError] = useState(null); const loadStatus = async () => { setLoading(true); setError(null); try { const data = await getStatus(); setStatus(data); setAgentForm({ maxTokens: data.max_tokens == null ? '' : String(data.max_tokens), temperature: String(data.temperature), maxToolIterations: String(data.max_tool_iterations), }); } catch (err: any) { setError(err.message || pickAppText(locale, '连接后端失败', 'Failed to connect to the backend')); } finally { setLoading(false); } }; useEffect(() => { loadStatus(); }, []); const loadConnectors = async () => { setLoadingConnectors(true); try { setConnectors(await listChannelConnectors()); } catch { setConnectors([]); } finally { setLoadingConnectors(false); } }; useEffect(() => { loadConnectors(); }, []); useEffect(() => { const sessionId = connectorSession?.session.sessionId; const status = connectorSession?.session.status; if (!sessionId || connectorSessionDone(status)) return; const timer = window.setInterval(() => { void pollConnectorSession(sessionId); }, 2500); return () => window.clearInterval(timer); }, [connectorSession?.session.sessionId, connectorSession?.session.status]); const openProviderDialog = (provider: ProviderStatus) => { setSelectedProvider(provider); setProviderError(null); setProviderForm({ enabled: Boolean(provider.enabled || provider.has_key), model: status?.model || '', apiKey: '', apiBase: provider.api_base || provider.default_api_base || provider.detail || '', requestTimeoutSeconds: '', }); }; const handleSaveProvider = async () => { if (!selectedProvider) return; const providerId = selectedProvider.id || selectedProvider.name; setSavingProvider(true); setProviderError(null); try { const timeout = providerForm.requestTimeoutSeconds.trim() ? Number(providerForm.requestTimeoutSeconds.trim()) : undefined; if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) { throw new Error(pickAppText(locale, '请求超时必须是正数', 'Request timeout must be a positive number')); } await updateProviderConfig(providerId, { enabled: providerForm.enabled, model: providerForm.model.trim() || undefined, api_key: providerForm.apiKey.trim() || undefined, api_base: providerForm.apiBase.trim() || undefined, request_timeout_seconds: timeout, }); await loadStatus(); setSelectedProvider(null); } catch (err: any) { setProviderError(err.message || pickAppText(locale, '保存提供商配置失败', 'Failed to save provider settings')); } finally { setSavingProvider(false); } }; const handleSaveAgentConfig = async () => { setSavingAgent(true); setAgentError(null); try { const maxTokensText = agentForm.maxTokens.trim(); const maxTokens = maxTokensText ? Number(maxTokensText) : null; const temperature = Number(agentForm.temperature.trim()); const maxToolIterations = Number(agentForm.maxToolIterations.trim()); if ( maxTokens !== null && (!Number.isInteger(maxTokens) || maxTokens <= 0) ) { throw new Error(pickAppText(locale, '最大令牌数必须为空或正整数', 'Max tokens must be blank or a positive integer')); } if (!Number.isFinite(temperature) || temperature < 0 || temperature > 2) { throw new Error(pickAppText(locale, '温度必须在 0 到 2 之间', 'Temperature must be between 0 and 2')); } if (!Number.isInteger(maxToolIterations) || maxToolIterations < 0) { throw new Error(pickAppText(locale, '最大工具迭代次数必须是非负整数', 'Max tool iterations must be a non-negative integer')); } await updateAgentConfig({ max_tokens: maxTokens, temperature, max_tool_iterations: maxToolIterations, }); await loadStatus(); } catch (err: any) { setAgentError(err.message || pickAppText(locale, '保存智能体配置失败', 'Failed to save agent configuration')); } finally { setSavingAgent(false); } }; const openChannelDetails = async (channel: ChannelStatus) => { setSelectedChannel(channel); setChannelConfig(null); setChannelForm(channelFormFromStatus(channel)); setChannelError(null); setChannelRestartRequired(false); setChannelEvents([]); setLoadingChannelConfig(true); setLoadingChannelEvents(true); try { const config = await getChannelConfig(channel.channel_id); setChannelConfig(config); setChannelForm(channelFormFromConfig(config)); } catch (err: any) { setChannelError(err.message || pickAppText(locale, '加载通道配置失败', 'Failed to load channel configuration')); } finally { setLoadingChannelConfig(false); } try { setChannelEvents(await listChannelEvents(channel.channel_id, 20)); } catch { setChannelEvents([]); } finally { setLoadingChannelEvents(false); } }; const handleSaveChannel = async () => { if (!selectedChannel) return; setSavingChannel(true); setChannelError(null); try { const payload = channelPayloadFromForm(channelForm); const result = await updateChannelConfig(selectedChannel.channel_id, payload); setChannelConfig(result.channel); setChannelForm(channelFormFromConfig(result.channel)); setChannelRestartRequired(Boolean(result.restart_required)); await loadStatus(); } catch (err: any) { setChannelError(err.message || pickAppText(locale, '保存通道配置失败', 'Failed to save channel configuration')); } finally { setSavingChannel(false); } }; const handleRestart = async () => { setRestarting(true); setRestartError(null); try { await restartRuntime(); setRestartOpen(false); window.setTimeout(() => { void loadStatus(); }, 5000); } catch (err: any) { setRestartError(err.message || pickAppText(locale, '重启失败', 'Restart failed')); } finally { setRestarting(false); } }; const openConnectorDialog = (connector: ChannelConnectorDescriptor) => { const kind = connector.kind; setConnectorDialogOpen(true); setConnectorSession(null); setConnectorError(null); setConnectorForm({ ...EMPTY_CONNECTOR_WIZARD, kind, displayName: connectorDisplayName(connector), domain: kind === 'feishu' ? 'feishu' : '', }); }; const handleStartConnectorSession = async () => { if (!connectorForm.kind || !SESSION_CONNECTOR_KINDS.has(connectorForm.kind)) return; setStartingConnector(true); setConnectorError(null); try { const options: Record = {}; if (connectorForm.kind === 'feishu') { options.domain = connectorForm.domain || 'feishu'; options.mode = connectorForm.mode; if (connectorForm.appId.trim()) options.appId = connectorForm.appId.trim(); if (connectorForm.appSecret.trim()) options.appSecret = connectorForm.appSecret.trim(); if (connectorForm.verificationToken.trim()) options.verificationToken = connectorForm.verificationToken.trim(); } const response = await startChannelConnectorSession({ kind: connectorForm.kind, displayName: connectorForm.displayName.trim() || connectorDisplayName({ kind: connectorForm.kind }), options, }); setConnectorSession(response); if (!connectorSessionDone(response.session.status)) { window.setTimeout(() => { void pollConnectorSession(response.session.sessionId); }, 1000); } } catch (err: any) { setConnectorError(err.message || pickAppText(locale, '启动连接失败', 'Failed to start connector session')); } finally { setStartingConnector(false); } }; const pollConnectorSession = async (sessionId: string) => { setPollingConnector(true); try { const response = await getChannelConnectorSession(sessionId); setConnectorSession(response); if (response.session.status === 'connected') { await loadStatus(); } } catch (err: any) { setConnectorError(err.message || pickAppText(locale, '刷新连接状态失败', 'Failed to refresh connector status')); } finally { setPollingConnector(false); } }; if (loading) { return (
); } if (error) { return (

{pickAppText(locale, '无法连接到 Boardware Agent Sandbox 后端', 'Unable to connect to the Boardware Agent Sandbox backend')}

{error}

{pickAppText(locale, '请确认后端服务已启动,并且当前页面可以访问它。', 'Please confirm the backend service is running and reachable from this page.')}

); } if (!status) return null; return (

{pickAppText(locale, '配置', 'Settings')}

{pickAppText( locale, '集中管理模型、工具、集成和实例运行状态。Task 和通知只在各自页面处理。', 'Manage models, tools, integrations, and instance runtime status. Tasks and notifications stay in their own pages.' )}

{/* System Info */} {pickAppText(locale, '实例运行', 'Instance runtime')}

{pickAppText(locale, '运行与调试', 'Runtime and debugging')}

{pickAppText(locale, '查看每次对话的运行日志和当前实例运行状态。', 'Inspect per-chat runtime logs and current instance status.')}

{status.runtime_controls?.self_restart !== false ? ( ) : null}
{/* Model Config */} {pickAppText(locale, '智能体配置', 'Agent configuration')}
setAgentForm((prev) => ({ ...prev, maxTokens: event.target.value }))} placeholder={pickAppText(locale, '模型默认', 'Model default')} />
setAgentForm((prev) => ({ ...prev, temperature: event.target.value }))} />
setAgentForm((prev) => ({ ...prev, maxToolIterations: event.target.value }))} />
{agentError || ''}
{/* Providers */} {pickAppText(locale, '提供商', 'Providers')}
{status.providers.map((p) => ( ))}
!open && setSelectedProvider(null)}> {pickAppText(locale, '配置提供商', 'Configure provider')} {selectedProvider ? ` · ${providerLabel(selectedProvider)}` : ''} {pickAppText(locale, '启用后会把它设为当前实例默认提供商。API Key 留空会保留已保存的值。', 'When enabled, this becomes the default provider for this instance. Leave API key empty to keep the saved value.')}

{pickAppText(locale, '关闭会从配置中移除这个提供商', 'Turning this off removes this provider from config')}

setProviderForm((prev) => ({ ...prev, enabled: checked }))} />
setProviderForm((prev) => ({ ...prev, model: event.target.value }))} placeholder="qwen-plus" disabled={!providerForm.enabled} />
setProviderForm((prev) => ({ ...prev, apiKey: event.target.value }))} placeholder={selectedProvider?.api_key_masked || pickAppText(locale, '留空保持不变', 'Leave blank to keep existing')} disabled={!providerForm.enabled || Boolean(selectedProvider?.is_oauth)} />
setProviderForm((prev) => ({ ...prev, apiBase: event.target.value }))} placeholder={selectedProvider?.default_api_base || 'https://api.example.com/v1'} disabled={!providerForm.enabled || Boolean(selectedProvider?.is_oauth)} />
setProviderForm((prev) => ({ ...prev, requestTimeoutSeconds: event.target.value }))} placeholder={pickAppText(locale, '默认', 'Default')} disabled={!providerForm.enabled} />
{providerError ? (

{providerError}

) : null}
!open && setSelectedChannel(null)}> {selectedChannel?.display_name || selectedChannel?.channel_id} {selectedChannel ? `${selectedChannel.kind}/${selectedChannel.mode} · ${selectedChannel.channel_id}` : ''} {selectedChannel ? (
{CONFIGURABLE_CHANNEL_KINDS.has(channelForm.kind) ? (

{pickAppText(locale, '连接配置', 'Connection settings')}

{pickAppText(locale, '凭据留空会保留已保存的值。保存后重启实例才会重新连接通道。', 'Leave credentials blank to keep saved values. Restart the instance after saving to reconnect channels.')}

setChannelForm((prev) => ({ ...prev, enabled: checked }))} disabled={loadingChannelConfig || savingChannel} />
setChannelForm((prev) => ({ ...prev, displayName: event.target.value }))} placeholder={selectedChannel.display_name || selectedChannel.channel_id} /> setChannelForm((prev) => ({ ...prev, accountId: event.target.value }))} placeholder="bot-main" />
{channelError ?

{channelError}

: null} {channelRestartRequired ? (
{pickAppText(locale, '配置已保存。重启实例后通道会按新配置启动。', 'Configuration saved. Restart the instance to start channels with the new settings.')}
) : null}
) : null}

{pickAppText(locale, '最近事件', 'Recent events')}

{loadingChannelEvents ? ( ) : (
{channelEvents.map((event) => (
{event.kind} {event.created_at}
{event.status}{event.error ? ` · ${event.error}` : ''}
))} {channelEvents.length === 0 ? (

{pickAppText(locale, '暂无事件', 'No events yet')}

) : null}
)}
) : null}
{pickAppText(locale, '重启实例?', 'Restart instance?')} {pickAppText( locale, '应用会短暂不可用,正在运行的对话和通道请求可能会中断。', 'The app will be unavailable briefly. Running chats and channel requests may be interrupted.' )} {restartError ?

{restartError}

: null}
{ setConnectorDialogOpen(open); if (!open) { setConnectorSession(null); setConnectorError(null); } }}> {connectorForm.kind ? connectorDisplayName({ kind: connectorForm.kind }) : pickAppText(locale, '连接通道', 'Connect channel')} {connectorForm.kind === 'weixin' ? pickAppText(locale, '使用扫码连接当前实例。', 'Connect this instance with QR login.') : connectorForm.kind === 'feishu' ? pickAppText(locale, '启动飞书/Lark 插件连接流程。', 'Start the Feishu/Lark plugin connection flow.') : pickAppText(locale, '选择连接方式。', 'Choose a connection method.')}
setConnectorForm((prev) => ({ ...prev, displayName: event.target.value }))} disabled={Boolean(connectorSession) || startingConnector} /> {connectorForm.kind === 'feishu' ? (
{!connectorSession ? (
{(connectorForm.mode === 'create' ? feishuCreateGuide(locale) : feishuLinkGuide(locale) ).map((item) => (

{item}

))}
) : null} {connectorForm.mode === 'link' ? (
setConnectorForm((prev) => ({ ...prev, appId: event.target.value }))} disabled={Boolean(connectorSession) || startingConnector} placeholder="cli_a..." /> setConnectorForm((prev) => ({ ...prev, appSecret: event.target.value }))} disabled={Boolean(connectorSession) || startingConnector} /> setConnectorForm((prev) => ({ ...prev, verificationToken: event.target.value }))} disabled={Boolean(connectorSession) || startingConnector} placeholder={pickAppText(locale, '事件订阅校验 Token', 'Event subscription token')} />
) : null}
) : null} {connectorSession ? (

{connectorSession.session.sessionId}

{connectorSession.connection?.channel_id || connectorSession.connection?.connection_id || '-'}

{connectorSession.session.status}
{connectorSession.session.qrImage ? (
{/* eslint-disable-next-line @next/next/no-img-element */} Connector QR
) : null} {connectorSession.session.instructions?.length ? (
{connectorSession.session.instructions.map((item, index) => (
{item}
))}
) : null} {connectorSession.session.accountId || connectorSession.session.displayName ? (
) : null} {connectorSession.session.error ? (

{connectorSession.session.error}

) : null}
) : null} {connectorError ?

{connectorError}

: null}
{connectorSession ? ( ) : SESSION_CONNECTOR_KINDS.has(connectorForm.kind) ? ( ) : null}
{/* Channels */} {pickAppText(locale, '通道', 'Channels')}
{(connectors.length ? connectors : [{ kind: 'telegram' }, { kind: 'weixin' }, { kind: 'feishu' }]).map((connector) => { const supportsSession = SESSION_CONNECTOR_KINDS.has(connector.kind); return ( ); })}
{loadingConnectors ? (
{pickAppText(locale, '正在加载连接器', 'Loading connectors')}
) : null}
{status.channels.length === 0 ? (

{pickAppText(locale, '尚未配置通道', 'No channels configured')}

) : ( status.channels.map((ch) => ( )) )}
); } function InfoRow({ label, value, ok, }: { label: string; value: string; ok?: boolean; }) { return (
{label}
{value} {ok !== undefined && (ok ? ( ) : ( ))}
); } function providerLabel(provider: ProviderStatus): string { return provider.label || provider.name; } function connectorDisplayName(connector: Pick): string { if (connector.displayName || connector.display_name) return connector.displayName || connector.display_name || connector.kind; if (connector.kind === 'weixin') return 'Weixin'; if (connector.kind === 'feishu') return 'Feishu/Lark'; if (connector.kind === 'telegram') return 'Telegram'; return connector.kind; } function connectorAuthLabel(connector: ChannelConnectorDescriptor, locale: AppLocale): string { const authType = connector.authType || connector.auth_type; if (connector.kind === 'weixin') return authType || pickAppText(locale, 'QR', 'QR'); if (connector.kind === 'feishu') return authType || pickAppText(locale, '插件', 'Plugin'); if (connector.kind === 'telegram') return authType || pickAppText(locale, 'Token', 'Token'); return authType || connector.kind; } function feishuCreateGuide(locale: AppLocale): string[] { return [ pickAppText(locale, '点击开始连接后会生成飞书扫码二维码。', 'Start connection to generate a Feishu/Lark QR code.'), pickAppText(locale, '用飞书客户端扫码,选择一键创建飞书机器人。', 'Scan with the Feishu/Lark client and choose one-click bot creation.'), pickAppText(locale, '创建完成后打开机器人,发送任意消息或 /feishu start 验证。', 'After creation, open the bot and send any message or /feishu start to verify.'), ]; } function feishuLinkGuide(locale: AppLocale): string[] { return [ pickAppText(locale, '关联已有机器人需要填写 App ID 和 App Secret。', 'Linking an existing bot requires App ID and App Secret.'), pickAppText(locale, '若提示凭证无效,请从飞书开放平台复制最新应用凭证。', 'If credentials are invalid, copy the latest app credentials from the Feishu/Lark developer console.'), ]; } function connectorSessionDone(status?: string | null): boolean { return ['connected', 'expired', 'error', 'cancelled'].includes(String(status || '')); } function connectorSessionBadgeVariant(status: string): 'default' | 'secondary' | 'destructive' | 'outline' { if (status === 'connected') return 'default'; if (status === 'error' || status === 'expired') return 'destructive'; if (status === 'cancelled') return 'secondary'; return 'outline'; } function channelStateBadgeVariant( state: ChannelStatus['state'] ): 'default' | 'secondary' | 'destructive' | 'outline' { if (state === 'running') return 'default'; if (state === 'error' || state === 'degraded') return 'destructive'; if (state === 'disabled' || state === 'stopped') return 'secondary'; return 'outline'; } function Field({ label, children }: { label: string; children: React.ReactNode }) { return (
{children}
); } function ChannelCredentialFields({ form, locale, maskedSecrets, setForm, }: { form: ChannelFormState; locale: AppLocale; maskedSecrets: Record; setForm: React.Dispatch>; }) { if (form.kind === 'telegram') { return (
setForm((prev) => ({ ...prev, botToken: event.target.value }))} placeholder={maskedSecrets.botToken || pickAppText(locale, '留空保持不变', 'Leave blank to keep existing')} /> setForm((prev) => ({ ...prev, botUsername: event.target.value }))} placeholder="beaver_bot" /> {form.mode === 'webhook' ? ( <> setForm((prev) => ({ ...prev, webhookUrl: event.target.value }))} placeholder="https://example.com/telegram" /> setForm((prev) => ({ ...prev, webhookSecret: event.target.value }))} placeholder={pickAppText(locale, '可选', 'Optional')} /> ) : null}
); } if (form.kind === 'feishu') { return (
setForm((prev) => ({ ...prev, appId: event.target.value }))} placeholder={maskedSecrets.appId || 'cli_a...'} /> setForm((prev) => ({ ...prev, appSecret: event.target.value }))} placeholder={maskedSecrets.appSecret || pickAppText(locale, '留空保持不变', 'Leave blank to keep existing')} /> setForm((prev) => ({ ...prev, botOpenId: event.target.value }))} placeholder={pickAppText(locale, '可选,用于群 mention 判断', 'Optional, for group mention checks')} />
); } if (form.kind === 'qqbot') { return (
setForm((prev) => ({ ...prev, appId: event.target.value }))} placeholder={maskedSecrets.appId || '1020...'} /> setForm((prev) => ({ ...prev, clientSecret: event.target.value }))} placeholder={maskedSecrets.clientSecret || pickAppText(locale, '留空保持不变', 'Leave blank to keep existing')} />

{pickAppText(locale, '按 QQ Bot markdown 消息发送文本。', 'Send text as QQ Bot markdown messages.')}

setForm((prev) => ({ ...prev, markdownSupport: checked }))} />
); } if (form.kind === 'weixin') { return (
setForm((prev) => ({ ...prev, token: event.target.value }))} placeholder={maskedSecrets.token || pickAppText(locale, '留空保持不变', 'Leave blank to keep existing')} /> setForm((prev) => ({ ...prev, baseUrl: event.target.value }))} placeholder={pickAppText(locale, '默认 iLink API', 'Default iLink API')} /> setForm((prev) => ({ ...prev, cdnBaseUrl: event.target.value }))} placeholder={pickAppText(locale, '默认 CDN', 'Default CDN')} /> setForm((prev) => ({ ...prev, textBatchDelaySeconds: event.target.value }))} placeholder="0.5" />
); } return null; } function ChannelPolicyFields({ form, locale, setForm, }: { form: ChannelFormState; locale: AppLocale; setForm: React.Dispatch>; }) { return (
{form.kind === 'telegram' || form.kind === 'feishu' ? (

{pickAppText(locale, '开启后群聊只有提到 bot 才会触发。', 'When enabled, group messages trigger only when they mention the bot.')}

setForm((prev) => ({ ...prev, requireMentionInGroups: checked }))} />
) : ( <> setForm((prev) => ({ ...prev, dmPolicy: value }))} /> setForm((prev) => ({ ...prev, groupPolicy: value }))} /> )}