import { useEffect, useState } from 'react'; import { Avatar, Button, Checkbox, Descriptions, Drawer, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { WECOM_BOT_PERMISSIONS, WECOM_BOT_PERMISSION_LABELS, WECOM_BOT_ROLE_DEFAULT_PERMISSIONS, WECOM_BOT_ROLE_LABELS, WECOM_BOT_ROLES, type LlmApiConfigOptionDto, type KnowledgeBaseOptionDto, type WecomBotDto, type WecomBotPermission, type WecomBotRole, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; import OssUpload from '../components/OssUpload'; type ListRes = { items: WecomBotDto[]; total: number; page: number; pageSize: number; runtime?: { masterEnabled: boolean; bots: Array<{ id: string; connected: boolean; lastError: string | null }>; }; }; type FormValues = { name: string; role: WecomBotRole; botId: string; secret?: string; avatarUrl?: string; welcome?: string; permissions: WecomBotPermission[]; aiEnabled: boolean; llmConfigId?: string | null; knowledgeBaseId?: string | null; enabled: boolean; sortOrder: number; }; export default function WecomBotsPage() { const [filterForm] = Form.useForm(); const [form] = Form.useForm(); const [filters, setFilters] = useState>({}); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const [detail, setDetail] = useState(null); const [runtime, setRuntime] = useState(); const [llmOptions, setLlmOptions] = useState([]); const [kbOptions, setKbOptions] = useState([]); const roleWatch = Form.useWatch('role', form); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/wecom-bots', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.role) qs.set('role', filters.role); if (filters.enabled) qs.set('enabled', filters.enabled); return qs; }, [filters], ); useEffect(() => { // useAdminList returns items; runtime comes from same API — fetch once for banner void request('/admin/wecom-bots?page=1&pageSize=1') .then((res) => setRuntime(res.runtime)) .catch(() => {}); }, [data]); useEffect(() => { void request<{ llmConfigs: LlmApiConfigOptionDto[]; knowledgeBases: KnowledgeBaseOptionDto[]; }>('/admin/wecom-bots/ai-options') .then((res) => { setLlmOptions(res.llmConfigs); setKbOptions(res.knowledgeBases); }) .catch(() => { setLlmOptions([]); setKbOptions([]); }); }, []); function openCreate() { setEditing(null); form.setFieldsValue({ name: '', role: 'CUSTOMER_SERVICE', botId: '', secret: '', avatarUrl: '', welcome: '', permissions: [...WECOM_BOT_ROLE_DEFAULT_PERMISSIONS.CUSTOMER_SERVICE], aiEnabled: false, llmConfigId: null, knowledgeBaseId: null, enabled: true, sortOrder: 0, }); setModalOpen(true); } function openEdit(row: WecomBotDto) { setEditing(row); form.setFieldsValue({ name: row.name, role: row.role, botId: row.botId, secret: '', avatarUrl: row.avatarUrl || '', welcome: row.welcome || '', permissions: row.permissions, aiEnabled: row.aiEnabled, llmConfigId: row.llmConfigId, knowledgeBaseId: row.knowledgeBaseId, enabled: row.enabled, sortOrder: row.sortOrder, }); setModalOpen(true); } async function submit() { const values = await form.validateFields(); setSaving(true); try { const payload = { name: values.name.trim(), role: values.role, botId: values.botId.trim(), avatarUrl: values.avatarUrl?.trim() || null, welcome: values.welcome?.trim() || null, permissions: values.permissions, aiEnabled: values.aiEnabled, llmConfigId: values.llmConfigId || null, knowledgeBaseId: values.knowledgeBaseId || null, enabled: values.enabled, sortOrder: values.sortOrder, }; if (editing) { await request(`/admin/wecom-bots/${editing.id}`, { method: 'PUT', body: JSON.stringify({ ...payload, secret: values.secret?.trim() || undefined, }), }); message.success('已更新'); } else { if (!values.secret?.trim()) { message.error('请填写 Secret'); return; } await request('/admin/wecom-bots', { method: 'POST', body: JSON.stringify({ ...payload, secret: values.secret.trim(), }), }); message.success('已创建'); } setModalOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } async function remove(id: string) { try { await request(`/admin/wecom-bots/${id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } } async function reloadConnections() { try { const st = await request<{ masterEnabled: boolean; bots: Array<{ id: string; connected: boolean; lastError: string | null }>; }>('/admin/wecom-bots/reload', { method: 'POST', body: '{}' }); message.success('已重载长连接'); setRuntime(st); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '重载失败'); } } const runtimeMap = new Map((runtime?.bots ?? []).map((b) => [b.id, b])); const columns: ColumnsType = [ { title: '头像', dataIndex: 'avatarUrl', width: 64, render: (url: string | null, row) => ( {row.name.slice(0, 1)} ), }, { title: '名称', dataIndex: 'name', width: 140, ellipsis: true }, { title: '角色', dataIndex: 'role', width: 120, render: (r: WecomBotRole) => WECOM_BOT_ROLE_LABELS[r] || r, }, { title: 'BotID', dataIndex: 'botId', width: 160, ellipsis: true }, { title: '权限', dataIndex: 'permissions', ellipsis: true, render: (perms: WecomBotPermission[]) => perms.map((p) => ( {WECOM_BOT_PERMISSION_LABELS[p] || p} )), }, { title: 'AI', width: 100, render: (_, row) => row.aiEnabled ? ( {row.llmConfigName || '已开'} ) : ( ), }, { title: '启用', dataIndex: 'enabled', width: 70, render: (v: boolean) => {v ? '是' : '否'}, }, { title: '连接', width: 80, render: (_, row) => { const rt = runtimeMap.get(row.id); if (!runtime?.masterEnabled) return 总开关关; if (!row.enabled) return 未启用; return ( {rt?.connected ? '已连接' : '未连接'} ); }, }, { title: '更新', dataIndex: 'updatedAt', width: 160, render: fmtTime }, { title: '操作', width: 180, fixed: 'right', render: (_, row) => ( remove(row.id)}> ), }, ]; return (
企微机器人 并列创建多个智能机器人,配置 BotID / Secret / 权限 / 角色 / 头像。总开关在「系统设置 → 功能开关」。 {runtime ? ` 当前总开关:${runtime.masterEnabled ? '开' : '关'}` : ''}
{ setFilters(v); setPage(1); }} >
{ setPage(p); setPageSize(ps); }, }} /> setModalOpen(false)} onOk={() => void submit()} confirmLoading={saving} width={640} destroyOnClose >
WECOM_BOT_PERMISSION_LABELS[p]).join('、') || '无'}` : undefined } > ({ value: p, label: WECOM_BOT_PERMISSION_LABELS[p], }))} /> ({ value: o.id, label: `${o.name}(${o.documentCount} 篇)`, }))} />
setDetail(null)}> {detail && ( {detail.name.slice(0, 1)} {detail.name} {WECOM_BOT_ROLE_LABELS[detail.role] || detail.role} {detail.botId} {detail.secretConfigured ? '已配置' : '未配置'} {detail.permissions.map((p) => WECOM_BOT_PERMISSION_LABELS[p] || p).join('、')} {detail.welcome || '—'} {detail.aiEnabled ? '开' : '关'} {detail.llmConfigName || '—'} {detail.knowledgeBaseName || '—'} {detail.enabled ? '是' : '否'} {detail.sortOrder} {fmtTime(detail.createdAt)} {fmtTime(detail.updatedAt)} )} ); }