import { useState } from 'react'; import { Button, Drawer, Form, Input, Modal, Popconfirm, Space, Switch, Table, Tag, Typography, Upload, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { UploadOutlined } from '@ant-design/icons'; import type { KnowledgeBaseDto, KnowledgeDocumentDetailDto, KnowledgeDocumentDto, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; import { uploadFileToOss } from '../lib/upload'; type FormValues = { name: string; description?: string; enabled: boolean; }; type DocForm = { title: string; contentText?: string; }; export default function KnowledgeBasesPage() { const [filterForm] = Form.useForm(); const [form] = Form.useForm(); const [docForm] = Form.useForm(); const [filters, setFilters] = useState>({}); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const [drawerKb, setDrawerKb] = useState(null); const [docs, setDocs] = useState([]); const [docsLoading, setDocsLoading] = useState(false); const [docModalOpen, setDocModalOpen] = useState(false); const [docSaving, setDocSaving] = useState(false); const [editingDoc, setEditingDoc] = useState(null); const [editDocModalOpen, setEditDocModalOpen] = useState(false); const [editDocSaving, setEditDocSaving] = useState(false); const [editDocForm] = Form.useForm(); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/knowledge-bases', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.enabled) qs.set('enabled', filters.enabled); return qs; }, [filters], ); const loadDocs = async (kb: KnowledgeBaseDto) => { setDrawerKb(kb); setDocsLoading(true); try { const res = await request<{ items: KnowledgeDocumentDto[] }>( `/admin/knowledge-bases/${kb.id}/documents`, ); setDocs(res.items); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } finally { setDocsLoading(false); } }; const openCreate = () => { setEditing(null); form.setFieldsValue({ name: '', description: '', enabled: true }); setModalOpen(true); }; const openEdit = (row: KnowledgeBaseDto) => { setEditing(row); form.setFieldsValue({ name: row.name, description: row.description || '', enabled: row.enabled, }); setModalOpen(true); }; const onSave = async () => { const values = await form.validateFields(); setSaving(true); try { if (editing) { await request(`/admin/knowledge-bases/${editing.id}`, { method: 'PUT', body: JSON.stringify(values), }); message.success('已保存'); } else { await request('/admin/knowledge-bases', { method: 'POST', body: JSON.stringify(values), }); message.success('已创建'); } setModalOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } finally { setSaving(false); } }; const columns: ColumnsType = [ { title: '名称', dataIndex: 'name' }, { title: '说明', dataIndex: 'description', ellipsis: true }, { title: '文档数', dataIndex: 'documentCount', width: 90 }, { title: '启用', dataIndex: 'enabled', width: 90, render: (v: boolean) => (v ? : ), }, { title: '创建人', dataIndex: 'createdByName', width: 100, render: (v: string | null, row) => v || row.createdByHqAccountId, }, { title: '更新时间', dataIndex: 'updatedAt', width: 170, render: (t: string) => fmtTime(t), }, { title: '操作', width: 240, render: (_, row) => ( {row.canEditFull ? ( ) : null} {row.canEditFull ? ( { try { await request(`/admin/knowledge-bases/${row.id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } }} > ) : null} ), }, ]; const docColumns: ColumnsType = [ { title: '标题', dataIndex: 'title' }, { title: '文件', dataIndex: 'fileName', ellipsis: true }, { title: '状态', dataIndex: 'status', width: 90, render: (s: string) => s === 'READY' ? 可检索 : s === 'FAILED' ? 失败 : 无正文, }, { title: '时间', dataIndex: 'createdAt', width: 170, render: (t: string) => fmtTime(t), }, { title: '操作', width: 140, render: (_, row) => drawerKb?.canEditFull ? ( { try { await request(`/admin/knowledge-bases/${drawerKb.id}/documents/${row.id}`, { method: 'DELETE', }); message.success('已删除'); void loadDocs(drawerKb); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } }} > ) : null, }, ]; return (
知识库 支持粘贴文本或上传 .txt/.md;可绑定到企微机器人供 AI 检索
setFilters({ name: v.name || '', enabled: v.enabled === undefined || v.enabled === null ? '' : String(v.enabled), }) } >
{ setPage(p); setPageSize(ps); }, }} /> setModalOpen(false)} onOk={() => void onSave()} confirmLoading={saving} destroyOnClose >
setDrawerKb(null)} extra={ drawerKb?.canEditFull ? ( ) : null } >
setDocModalOpen(false)} confirmLoading={docSaving} onOk={async () => { if (!drawerKb) return; const values = await docForm.validateFields(); setDocSaving(true); try { await request(`/admin/knowledge-bases/${drawerKb.id}/documents`, { method: 'POST', body: JSON.stringify({ title: values.title, contentText: values.contentText || null, }), }); message.success('已添加'); setDocModalOpen(false); void loadDocs(drawerKb); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } finally { setDocSaving(false); } }} destroyOnClose width={640} >
{ if (!drawerKb) return; try { const raw = file as File; const uploaded = await uploadFileToOss(raw, { bizType: 'KB_DOCUMENT', mediaType: 'FILE', }); const title = docForm.getFieldValue('title') || raw.name.replace(/\.[^.]+$/, ''); await request(`/admin/knowledge-bases/${drawerKb.id}/documents`, { method: 'POST', body: JSON.stringify({ title, fileName: raw.name, fileUrl: uploaded.url, mimeType: raw.type || null, sizeBytes: raw.size, }), }); message.success('上传成功'); setDocModalOpen(false); void loadDocs(drawerKb); reload(); onSuccess?.(uploaded); } catch (e) { const err = e instanceof Error ? e : new Error(String(e)); message.error(err.message); onError?.(err); } }} showUploadList={false} >
{ setEditDocModalOpen(false); setEditingDoc(null); }} confirmLoading={editDocSaving} onOk={async () => { if (!drawerKb || !editingDoc) return; const values = await editDocForm.validateFields(); setEditDocSaving(true); try { await request(`/admin/knowledge-bases/${drawerKb.id}/documents/${editingDoc.id}`, { method: 'PUT', body: JSON.stringify({ title: values.title, contentText: values.contentText || null, }), }); message.success('已保存'); setEditDocModalOpen(false); setEditingDoc(null); void loadDocs(drawerKb); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } finally { setEditDocSaving(false); } }} destroyOnClose width={640} >
); }