// @ts-nocheck import React, { useState, useEffect, useCallback } from 'react'; import { adminAPI, } from '@/api'; import { formatBjTime } from '@/utils/datetime'; // Platform icons (simple SVG representations) const platformIcons: Record = { feishu: 'Feishu', wechat: 'WeChat', personal_wechat: 'WeChat', wecom: 'WeCom', dingtalk: 'DingTalk', telegram: 'Telegram', whatsapp: 'WhatsApp', slack: 'Slack', }; const platformColors: Record = { feishu: '#3370FF', wechat: '#07C160', personal_wechat: '#07C160', wecom: '#2F80ED', dingtalk: '#0089FF', telegram: '#0088CC', whatsapp: '#25D366', slack: '#4A154B', }; const statusColors: Record = { connected: '#10b981', pending: '#f59e0b', disconnected: '#6b7280', }; type Tab = 'dashboard' | 'channels' | 'routes' | 'messages' | 'setup'; const ImBots: React.FC = () => { const [activeTab, setActiveTab] = useState('dashboard'); const [loading, setLoading] = useState(false); const [stats, setStats] = useState(null); const [dashboard, setDashboard] = useState(null); const [channels, setChannels] = useState([]); const [channelsTotal, setChannelsTotal] = useState(0); const [channelsPage, setChannelsPage] = useState(1); const [filterPlatform, setFilterPlatform] = useState(''); const [filterStatus, setFilterStatus] = useState(''); const [routes, setRoutes] = useState([]); const [messages, setMessages] = useState([]); const [messagesTotal, setMessagesTotal] = useState(0); const [messagesPage, setMessagesPage] = useState(1); const [platforms, setPlatforms] = useState([]); const [selectedChannel, setSelectedChannel] = useState(null); const [showChannelDetail, setShowChannelDetail] = useState(false); const [showSetupModal, setShowSetupModal] = useState(false); const [showRouteModal, setShowRouteModal] = useState(false); const [showWebhookModal, setShowWebhookModal] = useState(false); const [webhookInfo, setWebhookInfo] = useState(null); const [agents, setAgents] = useState([]); const [toast, setToast] = useState<{ msg: string; type: 'success' | 'error' } | null>(null); // Route form state const [routeForm, setRouteForm] = useState({ channel_id: '', agent_id: '', match_type: 'all', match_pattern: '', priority: 0, }); // Setup form state const [setupForm, setSetupForm] = useState({ platform: 'feishu', name: '', agent_id: '', credentials: '', }); const showToast = (msg: string, type: 'success' | 'error' = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; // Load agents for dropdowns const loadAgents = useCallback(async () => { try { const res = await adminAPI.getAIApps('?type=agent&page_size=100') as any; if (res?.data) { setAgents(res.data?.items || []); } } catch (e) { // silently fail } }, []); // Load stats const loadStats = useCallback(async () => { try { const res = await adminAPI.getImBotStats(); if ((res as any)?.success) setStats(res.data); } catch (e) { console.error('Stats error:', e); } }, []); // Load dashboard const loadDashboard = useCallback(async () => { try { const res = await adminAPI.getImBotDashboard(); if ((res as any)?.success) setDashboard(res.data); } catch (e) { console.error('Dashboard error:', e); } }, []); // Load channels const loadChannels = useCallback(async () => { setLoading(true); try { let endpoint = `?page=${channelsPage}&page_size=20`; if (filterPlatform) endpoint += `&platform=${filterPlatform}`; if (filterStatus) endpoint += `&status=${filterStatus}`; const res = await adminAPI.getImBotChannels(endpoint); if ((res as any)?.success) { setChannels(res.data?.items || []); setChannelsTotal(res.data?.total || 0); } } catch (e) { showToast('Failed to load channels', 'error'); } finally { setLoading(false); } }, [channelsPage, filterPlatform, filterStatus]); // Load routes const loadRoutes = useCallback(async () => { try { const res = await adminAPI.getImBotRoutes(); if ((res as any)?.success) setRoutes(res.data || []); } catch (e) { showToast('Failed to load routes', 'error'); } }, []); // Load messages const loadMessages = useCallback(async () => { try { const res = await adminAPI.getImBotMessages(`?page=${messagesPage}&page_size=50`); if ((res as any)?.success) { setMessages(res.data?.items || []); setMessagesTotal(res.data?.total || 0); } } catch (e) { showToast('Failed to load messages', 'error'); } }, [messagesPage]); // Load platforms const loadPlatforms = useCallback(async () => { try { const res = await adminAPI.getImBotPlatforms(); if ((res as any)?.success) setPlatforms(res.data || []); } catch (e) { // silently fail } }, []); // Initial load useEffect(() => { loadAgents(); loadStats(); loadPlatforms(); }, []); // Tab-specific loads useEffect(() => { if (activeTab === 'dashboard') { loadDashboard(); loadStats(); } else if (activeTab === 'channels') { loadChannels(); } else if (activeTab === 'routes') { loadRoutes(); loadChannels(); } else if (activeTab === 'messages') { loadMessages(); } }, [activeTab]); // Handle channel test const handleTestChannel = async (channelId: string) => { try { const res = await adminAPI.testImBotChannel(channelId); if ((res as any)?.success) { const data = res.data; if (data.passed) { showToast('Channel test passed!'); } else { showToast(`Test failed: ${data.checks.filter((c: any) => !c.passed).map((c: any) => c.name).join(', ')}`, 'error'); } } } catch (e) { showToast('Test failed', 'error'); } }; // Handle channel activate/deactivate const handleToggleChannel = async (channelId: string, currentActive: boolean) => { try { if (currentActive) { await adminAPI.deactivateImBotChannel(channelId); showToast('Channel deactivated'); } else { await adminAPI.activateImBotChannel(channelId); showToast('Channel activated'); } loadChannels(); loadStats(); } catch (e) { showToast('Operation failed', 'error'); } }; // Handle channel detail const handleViewChannel = async (channelId: string) => { try { const res = await adminAPI.getImBotChannelDetail(channelId); if ((res as any)?.success) { setSelectedChannel(res.data); setShowChannelDetail(true); } } catch (e) { showToast('Failed to load channel detail', 'error'); } }; // Handle webhook URL const handleGetWebhookUrl = async (platform: string) => { try { const res = await adminAPI.getImBotWebhookUrl(platform); if ((res as any)?.success) { setWebhookInfo(res.data); setShowWebhookModal(true); } } catch (e) { showToast('Failed to get webhook URL', 'error'); } }; // Handle create route const handleCreateRoute = async () => { if (!routeForm.channel_id || !routeForm.agent_id) { showToast('Please select channel and agent', 'error'); return; } try { const params = new URLSearchParams({ channel_id: routeForm.channel_id, agent_id: routeForm.agent_id, match_type: routeForm.match_type, priority: String(routeForm.priority), }); if (routeForm.match_pattern) params.append('match_pattern', routeForm.match_pattern); await adminAPI.createImBotRoute('?' + params.toString()); showToast('Route created successfully'); setShowRouteModal(false); setRouteForm({ channel_id: '', agent_id: '', match_type: 'all', match_pattern: '', priority: 0 }); loadRoutes(); } catch (e) { showToast('Failed to create route', 'error'); } }; // Handle delete route const handleDeleteRoute = async (routeId: string) => { if (!confirm('Delete this routing rule?')) return; try { await adminAPI.deleteImBotRoute(routeId); showToast('Route deleted'); loadRoutes(); } catch (e) { showToast('Failed to delete route', 'error'); } }; // Handle quick setup const handleQuickSetup = async () => { if (!setupForm.platform || !setupForm.name) { showToast('Please fill in platform and name', 'error'); return; } try { const params = new URLSearchParams({ platform: setupForm.platform, name: setupForm.name, }); if (setupForm.agent_id) params.append('agent_id', setupForm.agent_id); if (setupForm.credentials) params.append('credentials', setupForm.credentials); const res = await adminAPI.imBotQuickSetup('?' + params.toString()); if ((res as any)?.success) { const data = res.data; setWebhookInfo({ platform: data.platform, platform_name: data.platform_name, full_url: data.webhook_url, instructions: data.instructions, }); setShowSetupModal(false); setShowWebhookModal(true); showToast('Channel created! Follow the instructions to configure the webhook.'); setSetupForm({ platform: 'feishu', name: '', agent_id: '', credentials: '' }); loadChannels(); loadStats(); } } catch (e) { showToast('Quick setup failed', 'error'); } }; // Copy to clipboard const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text).then(() => { showToast('Copied to clipboard'); }); }; // Format time const formatTime = (time: string) => { if (!time) return '-'; return formatBjTime(time); }; const tabs: { key: Tab; label: string; icon: string }[] = [ { key: 'dashboard', label: '仪表盘', icon: 'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z' }, { key: 'channels', label: '频道管理', icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' }, { key: 'routes', label: '消息路由', icon: 'M3.5 18.5l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 17l1.5 1.5z' }, { key: 'messages', label: '消息日志', icon: 'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z' }, { key: 'setup', label: '快速接入', icon: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z' }, ]; return (
{/* Header */}

IM 机器人管理

管理即时通讯平台接入与消息路由

{stats && (
)}
{/* Tabs */}
{tabs.map((tab) => ( ))}
{/* Toast */} {toast && (
{toast.msg}
)} {/* Dashboard Tab */} {activeTab === 'dashboard' && (
{/* Stat Cards */}
{/* Platform Distribution */}

平台分布

{platforms.map((p) => (
0 ? p.color + '40' : '#2a2d35'}`, }}>
{p.name.slice(0, 2)}
{p.name}
频道: {p.channel_count} 在线: {p.active_count}
))}
{/* Message Trend */} {dashboard?.message_trend && dashboard.message_trend.length > 0 && (

7 天消息趋势

{dashboard.message_trend.map((item: any, i: number) => { const maxVal = Math.max(...dashboard.message_trend.map((t: any) => (t.msg_in || 0) + (t.msg_out || 0)), 1); const total = (item.msg_in || 0) + (item.msg_out || 0); const height = (total / maxVal) * 100; return (
{total}
{item.stat_date?.slice(5)}
); })}
)} {/* Recent Channels */} {dashboard?.recent_channels && dashboard.recent_channels.length > 0 && (

最近频道

{dashboard.recent_channels.slice(0, 5).map((ch: any) => (
{ch.platform_info?.name?.slice(0, 2) || ch.platform.slice(0, 2)}
{ch.name}
{ch.msg_count} 消息 {ch.status}
))}
)}
)} {/* Channels Tab */} {activeTab === 'channels' && (
{/* Filters */}
{/* Channel Table */}
{['频道名称', '平台', '状态', 'Agent', '消息数', '最后活跃', '操作'].map((h) => ( ))} {loading ? ( ) : channels.length === 0 ? ( ) : ( channels.map((ch) => ( )) )}
{h}
加载中...
暂无频道,点击「新建频道」开始
{ch.name}
{ch.account_name || ch.description || '-'}
{ch.platform_info?.name || ch.platform} {ch.status === 'connected' ? '已连接' : ch.status === 'pending' ? '待配置' : '已断开'} {ch.agent_name || '-'} {ch.msg_count || 0} {formatTime(ch.last_active)}
{/* Pagination */} {channelsTotal > 20 && (
{channelsPage} / {Math.ceil(channelsTotal / 20)}
)}
)} {/* Routes Tab */} {activeTab === 'routes' && (

消息路由规则

{['路由名称', '频道', 'Agent', '匹配类型', '匹配规则', '优先级', '状态', '操作'].map((h) => ( ))} {routes.length === 0 ? ( ) : ( routes.map((r) => ( )) )}
{h}
暂无路由规则
{r.route_name} {r.channel_name || r.channel_id?.slice(0, 8)} {r.platform} {r.agent_name || r.agent_id?.slice(0, 8) || '-'} {r.match_type === 'all' ? '全部消息' : r.match_type === 'keyword' ? '关键词' : r.match_type === 'regex' ? '正则' : '发送者'} {r.match_pattern || '-'} {r.priority} {r.is_active ? '启用' : '禁用'}
)} {/* Messages Tab */} {activeTab === 'messages' && (
{['时间', '频道', '平台', '方向', '发送者', '内容', '状态'].map((h) => ( ))} {messages.length === 0 ? ( ) : ( messages.map((msg, i) => ( )) )}
{h}
暂无消息记录
{formatTime(msg.created_at)} {msg.channel_name || msg.channel_id?.slice(0, 8)} {msg.platform_info?.name || msg.platform} {msg.direction === 'in' ? '接收' : '发送'} {msg.sender_name || msg.sender_id || '-'} {msg.content || '-'} {msg.status || '-'}
{messagesTotal > 50 && (
{messagesPage} / {Math.ceil(messagesTotal / 50)}
)}
)} {/* Setup Tab */} {activeTab === 'setup' && (

快速接入向导

选择平台并填写基本信息,系统将自动创建频道并生成 Webhook 配置指南

{/* Platform selection cards */}
{platforms.map((p) => (
setSetupForm({ ...setupForm, platform: p.key })} style={{ padding: '12px', borderRadius: '8px', cursor: 'pointer', background: setupForm.platform === p.key ? p.color + '20' : '#0f1117', border: setupForm.platform === p.key ? `1px solid ${p.color}` : '1px solid #2a2d35', textAlign: 'center', transition: 'all 0.2s', }} >
{p.name.slice(0, 2)}
{p.name}
))}
setSetupForm({ ...setupForm, name: e.target.value })} placeholder="例如:飞书客服机器人" style={inputStyle} />