/**
* 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 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: ['音乐', '教育'] },
];
// 初始化数据
kbData = [...mockKbData];
// ==================== 工具函数 ====================
/**
* 获取状态标签信息
*/
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);
}
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} 项
全部 ${kbData.length}
已就绪 ${readyCount}
处理中 ${processingCount}
异常 ${errorCount}
${isLoading ? `
` : ''}
${isLoading ? renderSkeleton() : ''}
${!isLoading ? `
` : ''}
`;
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', showCreateKbModal);
// 搜索
container.querySelector('#kbSearchInput')?.addEventListener('input', debounce((e) => {
searchText = e.target.value;
currentPage = 1;
selectedRows.clear();
renderPage();
}, 300));
// 模拟加载
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);
lucide.createIcons({ elements: [modalOverlay] });
const close = () => {
modalOverlay.classList.remove('open');
setTimeout(() => modalOverlay.remove(), 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);
}
};
// 操作
window.__kbDetail = function(id) {
navigateTo('kb-detail?id=' + id);
};
window.__kbHitTest = function(id) {
navigateTo('kb-detail?id=' + id + '&tab=hittest');
};
window.__kbEdit = function(id) {
Toast.info('编辑知识库:' + 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);
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
Toast.success('索引重建任务已启动');
}
});
};
/**
* 导出知识库
* API: GET /api/v1/knowledge-bases/:id/export
*/
window.__kbExport = function(id) {
// TODO: 调用 API GET /api/v1/knowledge-bases/:id/export
Toast.info('正在导出知识库...');
};
/**
* 批量导出
* API: GET /api/v1/knowledge-bases/batch/export
*/
window.__kbBatchExport = function() {
// TODO: 调用 API GET /api/v1/knowledge-bases/batch/export
Toast.info(`正在导出 ${selectedRows.size} 个知识库...`);
};
/**
* 批量删除
* API: DELETE /api/v1/knowledge-bases/batch
*/
window.__kbBatchDelete = function() {
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();
renderPage();
Toast.success(`已删除 ${selectedRows.size + 1} 个知识库`);
}
});
};
// 导出弹窗函数
window.__showCreateKbModal = showCreateKbModal;
// ==================== 初始化 ====================
renderPage();
});