/** * TalkingQ Platform - 知识库管理页面 * 包含知识库列表、添加知识库弹窗、筛选、分页等功能 * * API预留: * - GET /api/v1/knowledge-bases - 获取知识库列表 * - POST /api/v1/knowledge-bases - 创建知识库 * - DELETE /api/v1/knowledge-bases/:id - 删除知识库 * - POST /api/v1/knowledge-bases/:id/rebuild - 重建索引 */ registerPage('knowledge-base', function(container) { // ==================== 状态管理 ==================== let currentFilter = 'all'; let searchText = ''; let selectedRows = new Set(); let currentPage = 1; let pageSize = 10; let sortField = 'updatedAt'; let sortOrder = 'desc'; let isLoading = true; let hasStartedInitialLoad = false; let kbData = []; // ==================== API预留 - 后端接口定义 ==================== /** * 获取知识库列表 * GET /api/v1/knowledge-bases * Query: { page, pageSize, filter, search, sort, order } * Response: { list: [], total, page, pageSize } */ const API_GET_LIST = '/api/v1/knowledge-bases'; /** * 删除知识库 * DELETE /api/v1/knowledge-bases/:id * Response: { success: true } */ const API_DELETE = '/api/v1/knowledge-bases/:id'; /** * 重建索引 * POST /api/v1/knowledge-bases/:id/rebuild * Response: { success: true, taskId: string } */ const API_REBUILD = '/api/v1/knowledge-bases/:id/rebuild'; /** * 批量删除 * DELETE /api/v1/knowledge-bases/batch * Body: { ids: string[] } * Response: { success: true, deleted: number } */ const API_BATCH_DELETE = '/api/v1/knowledge-bases/batch'; /** * 导出知识库 * GET /api/v1/knowledge-bases/:id/export * Response: 文件流 */ const API_EXPORT = '/api/v1/knowledge-bases/:id/export'; // ==================== 模拟数据 ==================== const mockKbData = [ { id: 'kb-001', name: '儿童科学百科', description: '包含各类儿童科学知识的百科全书', docs: 2840, size: '128MB', type: 'doc_search', typeName: '文档搜索', status: 'ready', createdAt: '2026-05-01 10:00:00', updatedAt: '2026-05-10 14:32:15', tags: ['儿童', '科学'] }, { id: 'kb-002', name: '故事素材库 V2', description: '精选各类儿童故事素材', docs: 5200, size: '340MB', type: 'doc_search', typeName: '文档搜索', status: 'ready', createdAt: '2026-04-28 09:00:00', updatedAt: '2026-05-09 09:15:42', tags: ['故事', '儿童'] }, { id: 'kb-003', name: '小学数学知识点', description: '小学数学知识体系整理', docs: 1560, size: '86MB', type: 'doc_search', typeName: '文档搜索', status: 'processing', createdAt: '2026-05-08 14:00:00', updatedAt: '2026-05-11 16:45:30', tags: ['数学', '教育'] }, { id: 'kb-004', name: '英语启蒙词典', description: '儿童英语启蒙词汇', docs: 3200, size: '210MB', type: 'image_qa', typeName: '图片问答', status: 'ready', createdAt: '2026-04-20 11:00:00', updatedAt: '2026-05-08 11:20:08', tags: ['英语', '启蒙'] }, { id: 'kb-005', name: '唐诗宋词精选', description: '经典唐诗宋词收录', docs: 890, size: '45MB', type: 'doc_search', typeName: '文档搜索', status: 'ready', createdAt: '2026-04-15 08:00:00', updatedAt: '2026-05-07 08:30:55', tags: ['诗词', '文学'] }, { id: 'kb-006', name: '历史人物故事', description: '历史名人故事集', docs: 1200, size: '78MB', type: 'video_search', typeName: '音视频搜索', status: 'error', createdAt: '2026-04-10 16:00:00', updatedAt: '2026-05-06 14:10:22', tags: ['历史', '故事'] }, { id: 'kb-007', name: '产品参数数据库', description: '公司产品技术参数', docs: 450, size: '32MB', type: 'data_query', typeName: '数据查询', status: 'ready', createdAt: '2026-05-05 10:00:00', updatedAt: '2026-05-11 10:00:00', tags: ['产品', '技术'] }, { id: 'kb-008', name: '音乐教育资源库', description: '儿童音乐教育资源', docs: 560, size: '65MB', type: 'video_search', typeName: '音视频搜索', status: 'uploading', createdAt: '2026-05-12 10:00:00', updatedAt: '2026-05-12 18:00:00', tags: ['音乐', '教育'] }, ]; // 初始化数据。页面刷新后回到 mock;SPA 内新建/编辑/删除会保留在全局目录中。 kbData = Array.isArray(window.__knowledgeBaseCatalog) && window.__knowledgeBaseCatalog.length ? window.__knowledgeBaseCatalog.map(item => ({ ...item, tags: [...(item.tags || [])] })) : mockKbData.map(item => ({ ...item, tags: [...(item.tags || [])] })); window.__knowledgeBaseCatalog = kbData; function syncKnowledgeBaseCatalog() { window.__knowledgeBaseCatalog = kbData; } function getKbById(id) { return kbData.find(kb => kb.id === id); } function getNowText() { const date = new Date(); const pad = value => String(value).padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; } function downloadTextFile(filename, content, mime = 'application/json;charset=utf-8') { const blob = new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); } // ==================== 工具函数 ==================== /** * 获取状态标签信息 */ function getStatusBadge(status) { const map = { ready: { label: '已就绪', cls: 'ready' }, processing: { label: '处理中', cls: 'processing' }, uploading: { label: '上传中', cls: 'uploading' }, error: { label: '异常', cls: 'error' }, }; return map[status] || { label: status, cls: 'default' }; } /** * 获取类型标签信息 */ function getTypeTag(type) { const types = { doc_search: { label: '文档搜索', cls: 'doc_search' }, data_query: { label: '数据查询', cls: 'data_query' }, image_qa: { label: '图片问答', cls: 'image_qa' }, video_search: { label: '音视频搜索', cls: 'video_search' }, }; return types[type] || { label: type, cls: '' }; } /** * 获取类型图标 */ function getTypeIcon(type) { const icons = { doc_search: 'file-text', data_query: 'table', image_qa: 'image', video_search: 'video' }; return icons[type] || 'database'; } /** * 格式化时间 */ function formatTime(timeStr) { if (!timeStr) return '-'; return timeStr; } /** * HTML转义 */ function escapeHtml(str) { if (!str) return ''; return str.replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } /** * 生成分页按钮HTML */ function generatePagination(current, total) { if (total <= 1) return ''; let html = ''; const maxVisible = 5; let start = Math.max(1, current - Math.floor(maxVisible / 2)); let end = Math.min(total, start + maxVisible - 1); if (end - start < maxVisible - 1) { start = Math.max(1, end - maxVisible + 1); } if (start > 1) { html += ``; if (start > 2) html += `...`; } for (let i = start; i <= end; i++) { html += ``; } if (end < total) { if (end < total - 1) html += `...`; html += ``; } return html; } // ==================== 数据处理 ==================== /** * 筛选数据 */ function getFilteredData() { let filtered = [...kbData]; // 状态筛选 if (currentFilter !== 'all') { filtered = filtered.filter(kb => kb.status === currentFilter); } // 搜索筛选 if (searchText) { const s = searchText.toLowerCase(); filtered = filtered.filter(kb => kb.name.toLowerCase().includes(s) || kb.description.toLowerCase().includes(s) || kb.id.toLowerCase().includes(s) || kb.tags.some(t => t.toLowerCase().includes(s)) ); } // 排序 filtered.sort((a, b) => { let valA = a[sortField]; let valB = b[sortField]; if (sortField === 'name' || sortField === 'typeName') { return sortOrder === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); } if (sortField === 'createdAt' || sortField === 'updatedAt') { valA = Date.parse(valA || '') || 0; valB = Date.parse(valB || '') || 0; } else { valA = Number(valA) || 0; valB = Number(valB) || 0; } return sortOrder === 'asc' ? valA - valB : valB - valA; }); return filtered; } // ==================== 渲染函数 ==================== /** * 渲染页面 */ function renderPage() { const filteredData = getFilteredData(); const total = filteredData.length; const totalPages = Math.ceil(total / pageSize) || 1; const displayData = filteredData.slice((currentPage - 1) * pageSize, currentPage * pageSize); // 统计 const readyCount = kbData.filter(k => k.status === 'ready').length; const processingCount = kbData.filter(k => k.status === 'processing' || k.status === 'uploading').length; const errorCount = kbData.filter(k => k.status === 'error').length; container.innerHTML = `
已选择 ${selectedRows.size}

知识库管理

管理和配置您的知识库,用于 AI 检索和问答

全部 ${kbData.length}
已就绪 ${readyCount}
处理中 ${processingCount}
异常 ${errorCount}
${isLoading ? `
加载中...
` : ''} ${isLoading ? renderSkeleton() : ''} ${!isLoading ? `
${displayData.length === 0 ? ` ` : displayData.map(kb => renderTableRow(kb)).join('')}
知识库名称 类型 数据量 状态 创建时间 更新时间 操作
${renderEmptyState()}
${total > 0 ? `
${total} 条记录,每页 ${pageSize} 条
${generatePagination(currentPage, totalPages)}
` : ''}
` : ''}
`; lucide.createIcons({ elements: [container] }); bindEvents(); } /** * 渲染表格行 */ function renderTableRow(kb) { return `
${escapeHtml(kb.name)}
${escapeHtml(kb.description) || '暂无描述'}
${getTypeTag(kb.type).label}
${kb.docs.toLocaleString()} 文档 ${kb.size}
${getStatusBadge(kb.status).label} ${formatTime(kb.createdAt)} ${formatTime(kb.updatedAt)}
`; } /** * 渲染空状态 */ function renderEmptyState() { return `
暂无知识库
点击上方「添加知识库」开始创建您的第一个知识库
`; } /** * 渲染Skeleton加载状态 */ function renderSkeleton() { return `
${[1,2,3,4,5].map(() => `
`).join('')}
`; } // ==================== 事件绑定 ==================== function bindEvents() { // 添加知识库 container.querySelector('#addKbBtn')?.addEventListener('click', () => navigateTo('kb-create')); // 搜索 container.querySelector('#kbSearchInput')?.addEventListener('input', debounce((e) => { searchText = e.target.value; currentPage = 1; selectedRows.clear(); renderPage(); }, 300)); // 模拟加载,只在首次进入页面时触发一次,避免重渲染打断下拉菜单等交互。 if (isLoading && !hasStartedInitialLoad) { hasStartedInitialLoad = true; setTimeout(() => { isLoading = false; renderPage(); }, 500); } } /** * 防抖函数 */ function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } // ==================== 添加知识库弹窗 ==================== /** * 显示创建知识库弹窗 */ function showCreateKbModal() { let searchValue = ''; let selectedType = 'all'; let selectedStatus = 'all'; let filteredKbData = [...kbData]; let selectedForAdd = new Set(); const renderKbList = () => { // 应用筛选 filteredKbData = kbData.filter(kb => { if (selectedType !== 'all' && kb.type !== selectedType) return false; if (selectedStatus !== 'all' && kb.status !== selectedStatus) return false; if (searchValue) { const matchName = kb.name.toLowerCase().includes(searchValue.toLowerCase()); const matchTags = kb.tags.some(t => t.toLowerCase().includes(searchValue.toLowerCase())); const matchDesc = kb.description?.toLowerCase().includes(searchValue.toLowerCase()); if (!matchName && !matchTags && !matchDesc) return false; } return true; }); const modalBody = document.querySelector('.kb-select-list-area'); if (!modalBody) return; if (filteredKbData.length === 0) { modalBody.innerHTML = `
未找到匹配的知识库
尝试其他搜索关键词或筛选条件
`; } else { modalBody.innerHTML = filteredKbData.map(kb => `
${escapeHtml(kb.name)} ${selectedForAdd.has(kb.id) ? '✓ 已选择' : ''}
${kb.docs.toLocaleString()} 文档 · ${kb.size} · ${getTypeTag(kb.type).label} · ${getStatusBadge(kb.status).label}
${kb.tags.map(t => `${t}`).join('')}
`).join(''); } // 更新统计 const summary = document.querySelector('.kb-select-info-summary'); if (summary) { summary.textContent = `共 ${filteredKbData.length} 个知识库${selectedForAdd.size > 0 ? `,已选择 ${selectedForAdd.size} 个` : ''}`; } lucide.createIcons({ elements: [modalBody] }); }; const modalOverlay = document.createElement('div'); modalOverlay.className = 'modal-overlay'; modalOverlay.innerHTML = ` `; document.body.appendChild(modalOverlay); document.body.classList.add('modal-open'); lucide.createIcons({ elements: [modalOverlay] }); const close = () => { modalOverlay.classList.remove('open'); setTimeout(() => { modalOverlay.remove(); if (!document.querySelector('.modal-overlay')) { document.body.classList.remove('modal-open'); } }, 200); }; // 关闭事件 modalOverlay.querySelector('.modal-close').addEventListener('click', close); modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) close(); }); // 绑定弹窗内事件 setTimeout(() => { // 搜索 const searchInput = document.getElementById('kbSelectSearch'); searchInput?.addEventListener('input', (e) => { searchValue = e.target.value; renderKbList(); }); // 类型筛选 document.querySelectorAll('#typeFilter .kb-select-type-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('#typeFilter .kb-select-type-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); selectedType = btn.dataset.type; renderKbList(); }); }); // 状态筛选 document.querySelectorAll('#statusFilter .kb-select-type-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('#statusFilter .kb-select-type-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); selectedStatus = btn.dataset.status; renderKbList(); }); }); renderKbList(); }, 100); // 全局函数 window.__closeAddKbModal = close; window.__toggleKbSelect = function(id, name) { if (selectedForAdd.has(id)) { selectedForAdd.delete(id); } else { selectedForAdd.add(id); } // 更新确认按钮状态 const confirmBtn = document.getElementById('confirmAddKbBtn'); const countBadge = document.getElementById('selectedCountBadge'); if (confirmBtn) { confirmBtn.disabled = selectedForAdd.size === 0; } if (countBadge) { countBadge.textContent = selectedForAdd.size > 0 ? `(${selectedForAdd.size})` : ''; } renderKbList(); }; window.__confirmAddKb = function() { if (selectedForAdd.size === 0) { Toast.warning('请先选择知识库'); return; } const selectedNames = Array.from(selectedForAdd).map(id => { const kb = kbData.find(k => k.id === id); return kb ? kb.name : id; }); Toast.success(`已添加 ${selectedForAdd.size} 个知识库到当前应用`); close(); }; window.__goToCreateKb = function() { close(); navigateTo('kb-create'); }; requestAnimationFrame(() => modalOverlay.classList.add('open')); } // ==================== 全局操作函数 ==================== // 筛选 window.__kbFilter = function(filter) { currentFilter = filter; currentPage = 1; selectedRows.clear(); renderPage(); }; // 排序 window.__kbSort = function(field) { if (sortField === field) { sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } else { sortField = field; sortOrder = 'asc'; } renderPage(); }; // 分页 window.__kbGoPage = function(page) { const totalPages = Math.ceil(getFilteredData().length / pageSize) || 1; if (page >= 1 && page <= totalPages) { currentPage = page; renderPage(); } }; // 选择 window.__kbToggleSelect = function(id, checked) { if (checked) { selectedRows.add(id); } else { selectedRows.delete(id); } renderPage(); }; window.__kbSelectAll = function(checked) { const displayData = getFilteredData().slice((currentPage - 1) * pageSize, currentPage * pageSize); if (checked) { displayData.forEach(kb => selectedRows.add(kb.id)); } else { selectedRows.clear(); } renderPage(); }; window.__kbClearSelection = function() { selectedRows.clear(); renderPage(); }; // 下拉菜单 let __kbCloseDropdownHandler = null; window.__kbToggleDropdown = function(id, btn, e) { if (e) e.stopPropagation(); const dropdown = btn.closest('.dropdown'); const menu = dropdown.querySelector('.dropdown-menu'); const isOpen = dropdown.classList.contains('open'); // 关闭所有下拉菜单 document.querySelectorAll('.dropdown').forEach(d => { d.classList.remove('open'); d.querySelector('.dropdown-menu').style.display = 'none'; }); document.removeEventListener('click', __kbCloseDropdownHandler); document.removeEventListener('mousedown', __kbCloseDropdownHandler); if (!isOpen) { dropdown.classList.add('open'); menu.style.display = 'block'; __kbCloseDropdownHandler = (ev) => { if (!dropdown.contains(ev.target)) { dropdown.classList.remove('open'); menu.style.display = 'none'; document.removeEventListener('mousedown', __kbCloseDropdownHandler); } }; setTimeout(() => { document.addEventListener('mousedown', __kbCloseDropdownHandler); }, 10); } }; function openEditKbModal(id) { const kb = getKbById(id); if (!kb) { Toast.warning('未找到该知识库'); return; } createModal({ title: '编辑知识库', size: 'lg', content: `
前端 mock 会立即更新当前列表;后端接入后调用 PUT /api/v1/knowledge-bases/:id。
`, confirmText: '保存', onConfirm: () => { const name = document.getElementById('kbEditName')?.value.trim(); if (!name) { Toast.warning('请输入知识库名称'); return false; } const type = document.getElementById('kbEditType')?.value || kb.type; const tags = (document.getElementById('kbEditTags')?.value || '') .split(/[,,]/) .map(tag => tag.trim()) .filter(Boolean); kb.name = name; kb.description = document.getElementById('kbEditDesc')?.value.trim() || ''; kb.type = type; kb.typeName = getTypeTag(type).label; kb.status = document.getElementById('kbEditStatus')?.value || kb.status; kb.tags = tags; kb.updatedAt = getNowText(); syncKnowledgeBaseCatalog(); renderPage(); Toast.success('知识库信息已更新'); } }); lucide.createIcons(); } function exportKnowledgeBases(ids) { const items = ids.map(id => getKbById(id)).filter(Boolean); if (!items.length) { Toast.warning('请先选择要导出的知识库'); return; } createModal({ title: items.length > 1 ? '批量导出知识库' : '导出知识库', content: `
${items.map(item => escapeHtml(item.name)).join('、')}
浏览器会下载到默认下载目录;后端接入后替换为 GET /api/v1/knowledge-bases/:id/export。
`, confirmText: '确认导出', onConfirm: () => { const format = document.getElementById('kbExportFormat')?.value || 'json'; const includeStats = document.getElementById('kbExportIncludeStats')?.checked !== false; const exportRows = items.map(item => ({ id: item.id, name: item.name, description: item.description, type: item.type, typeName: item.typeName, ...(includeStats ? { docs: item.docs, size: item.size, status: item.status, tags: item.tags, } : {}), createdAt: item.createdAt, updatedAt: item.updatedAt, })); const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, ''); if (format === 'csv') { const headers = Object.keys(exportRows[0]); const csv = [ headers.join(','), ...exportRows.map(row => headers.map(key => `"${String(Array.isArray(row[key]) ? row[key].join('|') : row[key] ?? '').replace(/"/g, '""')}"`).join(',')), ].join('\n'); downloadTextFile(`knowledge-bases-${stamp}.csv`, csv, 'text/csv;charset=utf-8'); } else { downloadTextFile(`knowledge-bases-${stamp}.json`, JSON.stringify(exportRows, null, 2)); } Toast.success(`已导出 ${items.length} 个知识库`); } }); } // 操作 window.__kbDetail = function(id) { navigateTo('kb-detail?id=' + id); }; window.__kbHitTest = function(id) { navigateTo('kb-detail?id=' + id + '&tab=hittest'); }; window.__kbEdit = function(id) { openEditKbModal(id); }; /** * 删除知识库 * API: DELETE /api/v1/knowledge-bases/:id */ window.__kbDelete = function(id) { const kb = kbData.find(k => k.id === id); createModal({ title: '删除知识库', content: `

确定要删除知识库「${kb?.name}」吗?

此操作不可恢复

关联应用将无法继续使用该知识库,所有索引数据将被永久删除。

`, confirmText: '删除', confirmClass: 'btn-danger', onConfirm: () => { // TODO: 调用 API DELETE /api/v1/knowledge-bases/:id const idx = kbData.findIndex(k => k.id === id); if (idx !== -1) kbData.splice(idx, 1); selectedRows.delete(id); syncKnowledgeBaseCatalog(); renderPage(); Toast.success('知识库已删除'); } }); lucide.createIcons(); }; /** * 重建索引 * API: POST /api/v1/knowledge-bases/:id/rebuild */ window.__kbRebuildIndex = function(id) { const kb = kbData.find(k => k.id === id); createModal({ title: '重建索引', content: `

确定要重建知识库「${kb?.name}」的索引吗?

重建过程中知识库可正常访问,预计需要几分钟时间。

`, confirmText: '重建', onConfirm: () => { // TODO: 调用 API POST /api/v1/knowledge-bases/:id/rebuild if (kb) { kb.status = 'processing'; kb.updatedAt = getNowText(); syncKnowledgeBaseCatalog(); renderPage(); } Toast.success('索引重建任务已启动'); } }); }; /** * 导出知识库 * API: GET /api/v1/knowledge-bases/:id/export */ window.__kbExport = function(id) { exportKnowledgeBases([id]); }; /** * 批量导出 * API: GET /api/v1/knowledge-bases/batch/export */ window.__kbBatchExport = function() { exportKnowledgeBases(Array.from(selectedRows)); }; /** * 批量删除 * API: DELETE /api/v1/knowledge-bases/batch */ window.__kbBatchDelete = function() { if (selectedRows.size === 0) { Toast.warning('请先选择要删除的知识库'); return; } const deleteCount = selectedRows.size; createModal({ title: '批量删除', content: `

确定要删除选中的 ${selectedRows.size} 个知识库吗?

此操作不可恢复

所有选中的知识库及其索引数据将被永久删除。

`, confirmText: '删除', confirmClass: 'btn-danger', onConfirm: () => { // TODO: 调用 API DELETE /api/v1/knowledge-bases/batch selectedRows.forEach(id => { const idx = kbData.findIndex(k => k.id === id); if (idx !== -1) kbData.splice(idx, 1); }); selectedRows.clear(); syncKnowledgeBaseCatalog(); renderPage(); Toast.success(`已删除 ${deleteCount} 个知识库`); } }); }; // 导出弹窗函数 window.__showCreateKbModal = function() { navigateTo('kb-create'); }; // ==================== 初始化 ==================== renderPage(); });