// @ts-nocheck import React, { useState, useEffect, useCallback } from 'react'; import { useSearchParams } from 'react-router-dom'; import { imUserAPI } from '@/api'; const PRIMARY = '#5B5FED'; const PRIMARY_ACTIVE = '#4A4ECC'; // 后端按北京时间存储(无时区标记的 ISO),此处显式按 Asia/Shanghai 渲染, // 避免浏览器本地时区导致对话时间错乱(与个人微信绑定页一致)。 function formatBjTime(t?: string): string { if (!t) return ''; const iso = /[Z+]/.test(t) ? t : t + '+08:00'; const d = new Date(iso); if (isNaN(d.getTime())) return t; return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }); } const ImBotHub: React.FC = () => { const [searchParams, setSearchParams] = useSearchParams(); const highlight = searchParams.get('platform'); const [loading, setLoading] = useState(true); const [schemas, setSchemas] = useState([]); const [statuses, setStatuses] = useState>({}); const [activeConfig, setActiveConfig] = useState(null); const [historyPlatform, setHistoryPlatform] = useState(null); const [history, setHistory] = useState({ messages: [] }); const [historyLoading, setHistoryLoading] = useState(false); const [toast, setToast] = useState<{ msg: string; type: 'success' | 'error' } | null>(null); const [saving, setSaving] = useState(false); const showToast = (msg: string, type: 'success' | 'error' = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; const load = useCallback(async () => { try { const [pRes, lRes] = await Promise.all([imUserAPI.getPlatforms(), imUserAPI.listPlatforms()]); if (pRes?.success) setSchemas(pRes.data || []); if (lRes?.success) { const map: Record = {}; (lRes.data || []).forEach((it: any) => { map[it.platform] = it; }); setStatuses(map); } } catch (e) { showToast('加载平台列表失败', 'error'); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); useEffect(() => { if (highlight && schemas.length) { const el = document.getElementById(`imcard-${highlight}`); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, [highlight, schemas]); const openConfig = (key: string) => setActiveConfig(key); const closeConfig = () => setActiveConfig(null); const openHistory = async (key: string) => { setHistoryPlatform(key); setHistoryLoading(true); try { const res = await imUserAPI.platformHistory(key); if (res?.success) setHistory(res.data || { messages: [] }); } catch (e) { showToast('加载对话记录失败', 'error'); } finally { setHistoryLoading(false); } }; const handleDisconnect = async (key: string) => { try { const res = await imUserAPI.disconnectPlatform(key); if (res?.success) { showToast('已断开连接'); await load(); } else { showToast(res?.error || '断开失败', 'error'); } } catch (e) { showToast('断开失败', 'error'); } }; const getSchema = (key: string) => schemas.find((s) => s.key === key); const getStatus = (key: string) => statuses[key] || { configured: false, status: 'disconnected' }; return (

IM 机器人

把企业级即时通讯接入七虎AI 大脑:配置官方开放平台凭证后,消息经本平台自动应答并回写。支持企业微信、飞书、QQ、悟空IM。

{toast && (
{toast.msg}
)} {loading ? (
加载中…
) : (
{schemas.map((s) => { const st = getStatus(s.key); const connected = st.status === 'connected'; const badge = connected ? { bg: '#10b98120', color: '#10b981', text: '● 已连接' } : st.configured ? { bg: '#f59e0b20', color: '#f59e0b', text: '○ 已配置' } : { bg: '#6b728020', color: '#6b7280', text: '○ 未连接' }; return (
{s.name.slice(0, 1)}
{s.name}
{badge.text}

{s.description}

{connected && }
{st.last_error && (
⚠ {st.last_error}
)}
); })}
)} {activeConfig && ( { await load(); closeConfig(); showToast('配置已保存并启用'); }} onError={(m) => showToast(m, 'error')} saving={saving} setSaving={setSaving} /> )} {historyPlatform && ( setHistoryPlatform(null)} /> )}
); }; const ConfigModal: React.FC<{ schema: any; status: any; onClose: () => void; onSaved: () => void; onError: (m: string) => void; saving: boolean; setSaving: (b: boolean) => void }> = ({ schema, status, onClose, onSaved, onError, saving, setSaving }) => { const [form, setForm] = useState({}); const [sendUrl, setSendUrl] = useState(''); const [webhook, setWebhook] = useState(''); useEffect(() => { // 预填:若已配置,仅显示占位(机密已脱敏) if (status?.configured) { const c = status.creds || {}; const f: any = {}; (schema?.fields || []).forEach((fld: any) => { f[fld.key] = c[fld.key] && String(c[fld.key]).startsWith('***') ? '' : (c[fld.key] || ''); }); setForm(f); setSendUrl(''); } }, [schema, status]); const fetchWebhook = async () => { try { const res = await imUserAPI.platformWebhookUrl(schema.key); if (res?.success) setWebhook(res.data.webhook_url); } catch (e) { /* ignore */ } }; const handleSave = async () => { // 校验必填 for (const fld of (schema?.fields || [])) { if (fld.required && !String(form[fld.key] || '').trim()) { onError(`请填写:${fld.label}`); return; } } setSaving(true); try { const payload: any = { ...form }; if (sendUrl) payload.send_url = sendUrl; const res = await imUserAPI.savePlatformConfig(schema.key, payload); if (res?.success) { await fetchWebhook(); onSaved(); } else { onError(res?.error || '保存失败'); } } catch (e) { onError('保存失败'); } finally { setSaving(false); } }; return (
e.stopPropagation()} style={{ background: '#fff', borderRadius: '14px', width: '520px', maxWidth: '92vw', maxHeight: '88vh', overflowY: 'auto', padding: '24px' }}>

配置 {schema?.name}

{schema?.doc}

{(schema?.fields || []).map((fld: any) => (
setForm((p: any) => ({ ...p, [fld.key]: e.target.value }))} style={{ width: '100%', boxSizing: 'border-box', padding: '9px 12px', border: '1px solid #E5E7EB', borderRadius: '8px', fontSize: '14px', outline: 'none' }} />
))} {['qq', 'wukong'].includes(schema?.key) && (
setSendUrl(e.target.value)} style={{ width: '100%', boxSizing: 'border-box', padding: '9px 12px', border: '1px solid #E5E7EB', borderRadius: '8px', fontSize: '14px', outline: 'none' }} />
)} {webhook && (
回调地址(配置到 {schema?.name} 开放平台):
{webhook}
)}
); }; const HistoryModal: React.FC<{ schema: any; data: any; loading: boolean; onClose: () => void }> = ({ schema, data, loading, onClose }) => (
e.stopPropagation()} style={{ background: '#fff', borderRadius: '14px', width: '560px', maxWidth: '92vw', maxHeight: '88vh', overflowY: 'auto', padding: '24px' }}>

{schema?.name} · 对话记录

{loading ? (
加载中…
) : (data?.messages || []).length === 0 ? (
暂无对话记录
) : (
{(data.messages || []).map((m: any) => (
{m.content}
{formatBjTime(m.created_at)}
))}
)}
); const btnPrimary: React.CSSProperties = { padding: '8px 16px', background: PRIMARY, border: 'none', borderRadius: '8px', color: '#fff', fontSize: '14px', fontWeight: 500, cursor: 'pointer' }; const btnGhost: React.CSSProperties = { padding: '8px 14px', background: '#F0F2F8', border: '1px solid #E5E7EB', borderRadius: '8px', color: '#1A1F36', fontSize: '14px', fontWeight: 500, cursor: 'pointer' }; export default ImBotHub;