/** * TalkingQ Platform - 知识库详情页面 * 包含文件管理、标签管理、切片管理、命中测试等功能 * * API预留: * - GET /api/v1/knowledge-bases/:id - 获取知识库详情 * - GET /api/v1/knowledge-bases/:id/files - 获取文件列表 * - POST /api/v1/knowledge-bases/:id/files - 上传文件 * - DELETE /api/v1/knowledge-bases/:id/files/:fileId - 删除文件 * - POST /api/v1/knowledge-bases/:id/files/:fileId/reparse - 重新解析 * - GET /api/v1/knowledge-bases/:id/slices - 获取切片列表 * - PUT /api/v1/knowledge-bases/:id/slices/:sliceId - 更新切片 * - GET /api/v1/knowledge-bases/:id/hit-test - 命中测试 * - GET /api/v1/knowledge-bases/:id/metadata - 获取Metadata统计 */ registerPage('kb-detail', function(container) { // ==================== 状态管理 ==================== const urlParams = new URLSearchParams(window.location.search); const kbId = urlParams.get('id') || 'kb-001'; const initialTab = urlParams.get('tab') || 'files'; let currentTab = initialTab; let selectedFiles = new Set(); let isLoading = true; // ==================== API预留 - 后端接口定义 ==================== /** * 获取知识库详情 * GET /api/v1/knowledge-bases/:id * Response: { id, name, description, type, status, docs, size, tags, createdAt, updatedAt } */ const API_GET_DETAIL = '/api/v1/knowledge-bases/:id'; /** * 获取文件列表 * GET /api/v1/knowledge-bases/:id/files * Query: { page, pageSize, search, status } * Response: { list: [], total, page, pageSize } */ const API_GET_FILES = '/api/v1/knowledge-bases/:id/files'; /** * 上传文件 * POST /api/v1/knowledge-bases/:id/files * FormData: { file, category } * Response: { fileId, fileName, status } */ const API_UPLOAD_FILE = '/api/v1/knowledge-bases/:id/files'; /** * 删除文件 * DELETE /api/v1/knowledge-bases/:id/files/:fileId * Response: { success: true } */ const API_DELETE_FILE = '/api/v1/knowledge-bases/:id/files/:fileId'; /** * 重新解析文件 * POST /api/v1/knowledge-bases/:id/files/:fileId/reparse * Response: { success: true } */ const API_REPARSE_FILE = '/api/v1/knowledge-bases/:id/files/:fileId/reparse'; /** * 获取切片列表 * GET /api/v1/knowledge-bases/:id/slices * Query: { page, pageSize, search, fileId } * Response: { list: [], total, page, pageSize } */ const API_GET_SLICES = '/api/v1/knowledge-bases/:id/slices'; /** * 更新切片 * PUT /api/v1/knowledge-bases/:id/slices/:sliceId * Body: { content: string } * Response: { success: true, chars: number } */ const API_UPDATE_SLICE = '/api/v1/knowledge-bases/:id/slices/:sliceId'; /** * 获取切片历史版本 * GET /api/v1/knowledge-bases/:id/slices/:sliceId/history * Response: { list: [{ version, content, updatedAt, updatedBy }] } */ const API_SLICE_HISTORY = '/api/v1/knowledge-bases/:id/slices/:sliceId/history'; /** * 回滚切片 * POST /api/v1/knowledge-bases/:id/slices/:sliceId/rollback * Body: { version: number } * Response: { success: true } */ const API_ROLLBACK_SLICE = '/api/v1/knowledge-bases/:id/slices/:sliceId/rollback'; /** * 命中测试 * GET /api/v1/knowledge-bases/:id/hit-test * Query: { query, topK, similarityThreshold, mode } * Response: { results: [{ content, source, score, chars, metadata }] } */ const API_HIT_TEST = '/api/v1/knowledge-bases/:id/hit-test'; /** * 获取测试历史 * GET /api/v1/knowledge-bases/:id/test-history * Response: { list: [{ id, time, query, recallCount, avgScore }] } */ const API_TEST_HISTORY = '/api/v1/knowledge-bases/:id/test-history'; /** * 获取Metadata统计 * GET /api/v1/knowledge-bases/:id/metadata * Response: { tags: [], categories: [], formats: [] } */ const API_GET_METADATA = '/api/v1/knowledge-bases/:id/metadata'; /** * 重建索引 * POST /api/v1/knowledge-bases/:id/rebuild * Response: { success: true, taskId } */ const API_REBUILD_INDEX = '/api/v1/knowledge-bases/:id/rebuild'; /** * 删除知识库 * DELETE /api/v1/knowledge-bases/:id * Response: { success: true } */ const API_DELETE_KB = '/api/v1/knowledge-bases/:id'; // ==================== 模拟数据 ==================== const kbData = { id: kbId, name: '儿童科学百科', description: '包含各类儿童科学知识的百科全书,包括物理、化学、生物等基础科学知识', type: 'doc_search', typeName: '文档搜索', tags: ['儿童', '科学', '教育'], docs: 2840, size: '128MB', status: 'ready', createdAt: '2026-05-01 10:00:00', updatedAt: '2026-05-10 14:32:15', }; let kbFiles = [ { id: 'f1', name: '儿童科学百科.pdf', size: '2.5MB', format: 'PDF', dataSize: '2.5MB', status: 'ready', category: '默认类目', indexedAt: '2026-05-10 14:32:15', slices: 128, chars: 45678, parseError: null }, { id: 'f2', name: '科学小实验.docx', size: '1.8MB', format: 'DOCX', dataSize: '1.8MB', status: 'processing', category: '教学类目', indexedAt: '-', slices: 45, chars: 12340, parseError: null }, { id: 'f3', name: '物理入门.xlsx', size: '856KB', format: 'XLSX', dataSize: '856KB', status: 'ready', category: '默认类目', indexedAt: '2026-05-09 10:25:30', slices: 89, chars: 34560, parseError: null }, { id: 'f4', name: '化学元素表.pdf', size: '3.2MB', format: 'PDF', dataSize: '3.2MB', status: 'error', category: '教学类目', indexedAt: '-', slices: 0, chars: 0, parseError: 'PDF解析失败:文件格式损坏' }, { id: 'f5', name: '天文知识.png', size: '5.6MB', format: 'PNG', dataSize: '5.6MB', status: 'ready', category: '默认类目', indexedAt: '2026-05-08 16:45:22', slices: 34, chars: 8900, parseError: null }, { id: 'f6', name: '地理常识.docx', size: '2.1MB', format: 'DOCX', dataSize: '2.1MB', status: 'ready', category: '默认类目', indexedAt: '2026-05-07 11:20:15', slices: 67, chars: 23450, parseError: null }, ]; const mockSlices = [ { id: 's1', content: '光合作用是植物、藻类和某些细菌利用光能将二氧化碳和水转化为有机物并释放氧气的过程。这个过程主要发生在植物叶片的叶绿体中,是地球上最重要的生化反应之一。', source: '儿童科学百科.pdf', chars: 89, score: 0.95, sliceNo: 1, metadata: { title: '第一章 光合作用', author: '张三' } }, { id: 's2', content: '植物通过根部的根毛吸收土壤中的水分和无机盐,这些物质通过维管束运输到叶片。叶片中的气孔吸收空气中的二氧化碳,同时释放氧气和水蒸气。', source: '儿童科学百科.pdf', chars: 76, score: 0.88, sliceNo: 2, metadata: { title: '第一章 光合作用' } }, { id: 's3', content: '叶绿体是植物细胞中进行光合作用的主要场所,含有叶绿素等色素,赋予植物绿色的外观。叶绿体具有双层膜结构,内部有类囊体堆叠形成的基粒。', source: '儿童科学百科.pdf', chars: 68, score: 0.82, sliceNo: 3, metadata: { title: '第一章 光合作用' } }, ]; const testHistory = [ { id: 'h1', time: '2026-05-13 14:30:25', query: '光合作用是什么?', recallCount: 3, avgScore: 0.92 }, { id: 'h2', time: '2026-05-13 10:15:42', query: '植物如何吸收水分?', recallCount: 2, avgScore: 0.85 }, { id: 'h3', time: '2026-05-12 16:45:00', query: '什么是叶绿体?', recallCount: 4, avgScore: 0.78 }, ]; const quickQuestions = [ '什么是光合作用?', '植物如何吸收水分?', '叶绿体的作用是什么?', '光合作用需要什么条件?' ]; // ==================== 工具函数 ==================== function getFileIcon(format) { const icons = { PDF: '📄', DOCX: '📄', DOC: '📄', TXT: '📝', MD: '📋', XLSX: '📊', XLS: '📊', CSV: '📊', PNG: '🖼', JPG: '🖼', JPEG: '🖼', GIF: '🖼', BMP: '🖼', MP4: '🎬', AVI: '🎬', MOV: '🎬', MKV: '🎬', WMV: '🎬' }; return icons[format] || '📁'; } 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 || timeStr === '-') return '-'; return timeStr; } function escapeHtml(str) { return String(str || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function getStatusBadge(status) { const map = { ready: { label: '解析完成', cls: 'badge-success' }, processing: { label: '解析中', cls: 'badge-warning' }, error: { label: '解析失败', cls: 'badge-danger' }, uploading: { label: '上传中', cls: 'badge-info' }, }; return map[status] || { label: status, cls: 'badge-default' }; } 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 formatBytes(bytes) { const value = Number(bytes) || 0; if (value < 1024) return `${value}B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)}KB`; return `${(value / 1024 / 1024).toFixed(1)}MB`; } 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 renderPage() { container.innerHTML = `
/ 知识库管理 / ${kbData.name}
${kbData.name} ${kbData.status === 'ready' ? '已就绪' : kbData.status === 'processing' ? '处理中' : '异常'}
${kbData.description}
ID: ${kbData.id} 类型: ${kbData.typeName} 文档: ${kbData.docs.toLocaleString()} 数据量: ${kbData.size} 更新: ${formatTime(kbData.updatedAt)}
${kbData.tags.map(t => `${t}`).join('')}
文件管理
标签管理
切片管理
统计信息
${currentTab === 'files' ? renderFilesTab() : currentTab === 'tags' ? renderTagsTab() : currentTab === 'slices' ? renderSlicesTab() : renderMetadataTab()}
`; lucide.createIcons({ elements: [container] }); } // ==================== 文件管理Tab ==================== function renderFilesTab() { const readyCount = kbFiles.filter(f => f.status === 'ready').length; const processingCount = kbFiles.filter(f => f.status === 'processing' || f.status === 'uploading').length; const errorCount = kbFiles.filter(f => f.status === 'error').length; return `
${kbFiles.length}
总文件数
${readyCount}
已解析
${processingCount}
解析中
${errorCount}
解析失败
已选择 ${selectedFiles.size} 个文件
${renderFileRows()}
文件名 格式 大小 数据量 状态 所属类目 切片数 索引时间 操作
`; } function renderFileRows() { if (kbFiles.length === 0) { return `
暂无文件
点击上方「上传文件」添加文件
`; } return kbFiles.map(file => `
${getFileIcon(file.format)} ${file.name}
${file.format} ${file.size} ${file.dataSize} ${getStatusBadge(file.status).label} ${file.status === 'error' && file.parseError ? `
${file.parseError}
` : ''} ${file.category} ${file.slices > 0 ? `${file.slices}` : '-'} ${formatTime(file.indexedAt)}
`).join(''); } // ==================== 标签管理Tab ==================== function renderTagsTab() { return `

知识库标签

${kbData.tags.map((tag, idx) => `
${tag}
`).join('')}
标签字数限制:0-32 个字符,支持中英文、数字

标签使用统计

${kbData.tags.map(tag => `
${tag} ${Math.floor(Math.random() * 100 + 50)} 个文件
`).join('')}
`; } // ==================== 切片管理Tab ==================== function renderSlicesTab() { return `
${kbData.docs} 个切片
${mockSlices.map((slice, idx) => `
切片 #${slice.sliceNo} 来源: ${slice.source} · ${slice.chars} 字符
相关度: ${(slice.score * 100).toFixed(0)}%
${slice.content}
${slice.metadata ? `
${Object.entries(slice.metadata).map(([k,v]) => `${k}: ${v}`).join(' | ')}
` : ''}
`).join('')}
共 ${kbData.docs} 个切片
...
`; } // ==================== 统计信息Tab ==================== function renderMetadataTab() { return `
切片统计
总切片数 ${kbData.docs.toLocaleString()}
总字符数 1,245,678
平均切片长度 439 字符
文件格式分布
PDF 45%
DOCX 30%
XLSX 15%
其他 10%
Metadata 统计
提取的标题
${['第一章 光合作用', '第二章 呼吸作用', '第三章 物质运输', '第四章 生长素'].map(t => ` ${t} `).join('')}
关键词
${['光合作用', '叶绿体', '氧气', '二氧化碳', '水分'].map(k => ` ${k} `).join('')}
`; } // ==================== 全局函数 ==================== let __dropdownHandler = null; window.__switchTab = function(tab) { currentTab = tab; renderPage(); }; window.__toggleDetailDropdown = function(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', __dropdownHandler); if (!isOpen) { dropdown.classList.add('open'); menu.style.display = 'block'; __dropdownHandler = (ev) => { if (!dropdown.contains(ev.target)) { dropdown.classList.remove('open'); menu.style.display = 'none'; document.removeEventListener('click', __dropdownHandler); } }; setTimeout(() => document.addEventListener('click', __dropdownHandler), 10); } }; window.__toggleFileDropdown = function(id, btn, e) { window.__toggleDetailDropdown(btn, e); }; // 文件选择 window.__toggleFileSelect = function(id, checked) { if (checked) selectedFiles.add(id); else selectedFiles.delete(id); renderPage(); }; window.__toggleSelectAllFiles = function(checked) { if (checked) kbFiles.forEach(f => selectedFiles.add(f.id)); else selectedFiles.clear(); renderPage(); }; window.__clearFileSelection = function() { selectedFiles.clear(); renderPage(); }; window.__selectAllFiles = function() { kbFiles.forEach(f => selectedFiles.add(f.id)); renderPage(); }; window.__filterFiles = function(keyword) { const tbody = document.getElementById('fileTableBody'); if (!tbody) return; tbody.querySelectorAll('tr').forEach(row => { const name = row.querySelector('td:nth-child(2)')?.textContent.toLowerCase() || ''; row.style.display = name.includes(keyword.toLowerCase()) ? '' : 'none'; }); }; window.__filterSlices = function(keyword) { const query = String(keyword || '').trim().toLowerCase(); document.querySelectorAll('#sliceList .slice-list-item').forEach(item => { const text = item.textContent.toLowerCase(); item.style.display = text.includes(query) ? '' : 'none'; }); }; // 文件操作 window.__uploadFiles = function() { createModal({ title: '上传文件', size: 'lg', content: `
前端 mock 会将文件加入列表并进入“解析中”状态;后端接入后调用 POST /api/v1/knowledge-bases/:id/files。
`, confirmText: '上传', onConfirm: () => { const input = document.getElementById('detailUploadInput'); const files = Array.from(input?.files || []); if (!files.length) { Toast.warning('请先选择文件'); return false; } const category = document.getElementById('detailUploadCategory')?.value || '默认类目'; files.forEach(file => { const ext = file.name.split('.').pop()?.toUpperCase() || 'FILE'; kbFiles.unshift({ id: `f-${Date.now()}-${Math.random().toString(16).slice(2, 7)}`, name: file.name, size: formatBytes(file.size), format: ext, dataSize: formatBytes(file.size), status: 'processing', category, indexedAt: '-', slices: 0, chars: 0, parseError: null, }); }); kbData.docs += files.length; kbData.updatedAt = getNowText(); selectedFiles.clear(); renderPage(); Toast.success(`已上传 ${files.length} 个文件`); } }); }; window.__viewMetaInfo = function(fileId) { const file = kbFiles.find(f => f.id === fileId); createModal({ title: 'Meta 信息', content: `
文件名${file?.name || '-'}
文件大小${file?.size || '-'}
数据量${file?.dataSize || '-'}
文件格式${file?.format || '-'}
切片数量${file?.slices || 0} 个
字符总数${file?.chars?.toLocaleString() || 0} 字符
状态${getStatusBadge(file?.status).label}
所属类目${file?.category || '-'}
索引时间${formatTime(file?.indexedAt)}
`, confirmText: '', cancelText: '关闭' }); }; window.__viewSlices = function(fileId) { const file = kbFiles.find(f => f.id === fileId); createModal({ title: `查看切片 - ${file?.name || ''}`, size: 'lg', content: `
切片数量: ${file?.slices || 0} 总字符数: ${file?.chars?.toLocaleString() || 0}
${mockSlices.map((slice, idx) => `
切片 ${slice.sliceNo} ${slice.chars} 字符
${slice.content}
`).join('')}
`, confirmText: '', cancelText: '关闭' }); lucide.createIcons(); }; window.__reparseFile = function(fileId) { const file = kbFiles.find(f => f.id === fileId); createModal({ title: '重新解析', content: `

确定要重新解析文件「${file?.name}」吗?

重新解析将覆盖之前的解析结果。

`, confirmText: '重新解析', onConfirm: () => { // TODO: 调用 API POST /api/v1/knowledge-bases/:id/files/:fileId/reparse const idx = kbFiles.findIndex(f => f.id === fileId); if (idx !== -1) { kbFiles[idx].status = 'processing'; renderPage(); } Toast.success('重新解析任务已启动'); } }); }; window.__moveFile = function(fileId) { const file = kbFiles.find(f => f.id === fileId); if (!file) { Toast.warning('未找到文件'); return; } createModal({ title: '移动到类目', content: `

文件:${file.name}

`, confirmText: '移动', onConfirm: () => { file.category = document.getElementById('moveFileCategory')?.value || file.category; renderPage(); Toast.success('文件已移动'); } }); }; window.__deleteFile = function(fileId) { const file = kbFiles.find(f => f.id === fileId); createModal({ title: '删除文件', content: `

确定要删除文件「${file?.name}」吗?

此操作不可恢复,文件及其切片数据将被永久删除。
`, confirmText: '删除', confirmClass: 'btn-danger', onConfirm: () => { kbFiles = kbFiles.filter(f => f.id !== fileId); renderPage(); Toast.success('文件已删除'); } }); lucide.createIcons(); }; // 批量操作 window.__reparseSelectedFiles = function() { if (selectedFiles.size === 0) { Toast.warning('请先选择文件'); return; } createModal({ title: '重新解析', content: `

确定要重新解析选中的 ${selectedFiles.size} 个文件吗?

`, confirmText: '重新解析', onConfirm: () => { // TODO: 调用批量重新解析API selectedFiles.forEach(id => { const idx = kbFiles.findIndex(f => f.id === id); if (idx !== -1) kbFiles[idx].status = 'processing'; }); selectedFiles.clear(); renderPage(); Toast.success('重新解析任务已启动'); } }); }; window.__moveSelectedFiles = function() { if (selectedFiles.size === 0) { Toast.warning('请先选择文件'); return; } createModal({ title: '批量移动类目', content: `

将选中的 ${selectedFiles.size} 个文件移动到:

`, confirmText: '移动', onConfirm: () => { const category = document.getElementById('moveSelectedCategory')?.value || '默认类目'; selectedFiles.forEach(id => { const file = kbFiles.find(f => f.id === id); if (file) file.category = category; }); selectedFiles.clear(); renderPage(); Toast.success('文件已移动'); } }); }; window.__deleteSelectedFiles = function() { if (selectedFiles.size === 0) { Toast.warning('请先选择文件'); return; } createModal({ title: '批量删除', content: `

确定要删除选中的 ${selectedFiles.size} 个文件吗?

此操作不可恢复。
`, confirmText: '删除', confirmClass: 'btn-danger', onConfirm: () => { kbFiles = kbFiles.filter(f => !selectedFiles.has(f.id)); selectedFiles.clear(); renderPage(); Toast.success('文件已删除'); } }); }; // 切片编辑 window.__editSlice = function(sliceId) { const slice = mockSlices.find(s => s.id === sliceId); createModal({ title: '编辑切片', size: 'lg', content: `
来源: ${slice?.source} · 切片 ${slice?.sliceNo} · ${slice?.chars} 字符
`, confirmText: '保存', onConfirm: () => { const newContent = document.getElementById('sliceEditor')?.value || ''; const idx = mockSlices.findIndex(s => s.id === sliceId); if (idx !== -1) { mockSlices[idx].content = newContent; mockSlices[idx].chars = newContent.length; } Toast.success('切片已保存'); } }); setTimeout(() => { const editor = document.getElementById('sliceEditor'); const counter = document.getElementById('charCount'); if (editor && counter) { editor.addEventListener('input', () => { counter.textContent = editor.value.length; }); } }, 100); }; window.__showSliceHistory = function(sliceId) { createModal({ title: '切片历史版本', content: `
${[1,2,3].map(v => `
版本 ${v}
2026-05-${10-v} 14:30:25 · 张三
`).join('')}
`, confirmText: '', cancelText: '关闭' }); }; window.__rollbackSlice = function(sliceId, version) { Toast.success(`已回滚到版本 ${version}`); }; // 标签管理 window.__showAddTag = function() { createModal({ title: '新增标签', content: `
0/32
标签字数限制:0-32 个字符
`, confirmText: '添加', onConfirm: () => { const name = document.getElementById('newTagName')?.value.trim(); if (name) { kbData.tags.push(name); renderPage(); Toast.success('标签已添加'); } } }); setTimeout(() => { const input = document.getElementById('newTagName'); const counter = document.getElementById('tagCharCounter'); if (input && counter) { input.addEventListener('input', () => { counter.textContent = `${input.value.length}/32`; }); } }, 100); }; window.__addNewTag = function() { const input = document.getElementById('newTagInput'); if (input?.value.trim()) { kbData.tags.push(input.value.trim()); input.value = ''; renderPage(); Toast.success('标签已添加'); } }; window.__removeTag = function(index) { kbData.tags.splice(index, 1); renderPage(); Toast.success('标签已移除'); }; window.__editTags = function() { currentTab = 'tags'; renderPage(); }; // 命中测试 window.__showHitTest = function() { createModal({ title: '命中率测试', size: 'lg', content: `
${quickQuestions.map(q => ` ${q} `).join('')}
搜索结果

输入问题并点击测试查看结果

历史召回记录
${testHistory.map(h => `
${h.time}
问: ${h.query}
召回 ${h.recallCount} 个切片,相关度 ${(h.avgScore * 100).toFixed(0)}%
`).join('')}
`, confirmText: '', cancelText: '关闭' }); lucide.createIcons(); }; window.__runHitTest = function() { const query = document.getElementById('testQuery')?.value.trim(); if (!query) { Toast.error('请输入测试问题'); return; } // TODO: 调用 API GET /api/v1/knowledge-bases/:id/hit-test const resultsHtml = mockSlices.map(slice => `
${slice.source} 相关度: ${(slice.score * 100).toFixed(0)}%
${slice.chars} 字符 切片 ${slice.sliceNo}
${slice.content}
`).join(''); document.getElementById('testResults').innerHTML = resultsHtml; lucide.createIcons(); Toast.success('测试完成'); }; // 知识库操作 window.__editKb = function() { createModal({ title: '编辑知识库', size: 'lg', content: `
`, confirmText: '保存', onConfirm: () => { const name = document.getElementById('detailKbName')?.value.trim(); if (!name) { Toast.warning('请输入知识库名称'); return false; } kbData.name = name; kbData.description = document.getElementById('detailKbDesc')?.value.trim() || ''; kbData.tags = (document.getElementById('detailKbTags')?.value || '') .split(/[,,]/) .map(tag => tag.trim()) .filter(Boolean); kbData.updatedAt = getNowText(); const catalog = Array.isArray(window.__knowledgeBaseCatalog) ? window.__knowledgeBaseCatalog : []; const target = catalog.find(item => item.id === kbData.id); if (target) Object.assign(target, { name: kbData.name, description: kbData.description, tags: [...kbData.tags], updatedAt: kbData.updatedAt, }); renderPage(); Toast.success('知识库信息已更新'); } }); }; window.__rebuildAllIndex = function() { createModal({ title: '重建全部索引', content: `

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

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

`, confirmText: '重建', onConfirm: () => { // TODO: 调用 API POST /api/v1/knowledge-bases/:id/rebuild kbData.status = 'processing'; kbData.updatedAt = getNowText(); const catalog = Array.isArray(window.__knowledgeBaseCatalog) ? window.__knowledgeBaseCatalog : []; const target = catalog.find(item => item.id === kbData.id); if (target) { target.status = kbData.status; target.updatedAt = kbData.updatedAt; } renderPage(); Toast.success('索引重建任务已启动'); } }); }; window.__reparseAll = function() { createModal({ title: '重新解析全部', content: `

确定要重新解析知识库「${kbData.name}」的全部文件吗?

此操作将重新解析所有文件,请耐心等待。

`, confirmText: '重新解析', onConfirm: () => { kbFiles = kbFiles.map(file => ({ ...file, status: 'processing', indexedAt: '-', parseError: null, })); kbData.status = 'processing'; kbData.updatedAt = getNowText(); const catalog = Array.isArray(window.__knowledgeBaseCatalog) ? window.__knowledgeBaseCatalog : []; const target = catalog.find(item => item.id === kbData.id); if (target) { target.status = kbData.status; target.updatedAt = kbData.updatedAt; } selectedFiles.clear(); renderPage(); Toast.success('重新解析任务已启动'); } }); }; window.__exportKb = function() { createModal({ title: '导出知识库', content: `
浏览器会下载到默认下载目录;后端接入后替换为 GET /api/v1/knowledge-bases/:id/export。
`, confirmText: '确认导出', onConfirm: () => { const format = document.getElementById('detailExportFormat')?.value || 'json'; const includeFiles = document.getElementById('detailExportIncludeFiles')?.checked !== false; const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, ''); if (format === 'csv') { const rows = [ 'id,name,format,size,status,category,slices,chars,indexedAt', ...kbFiles.map(file => [ file.id, file.name, file.format, file.size, file.status, file.category, file.slices, file.chars, file.indexedAt, ].map(value => `"${String(value ?? '').replace(/"/g, '""')}"`).join(',')), ].join('\n'); downloadTextFile(`${kbData.id}-files-${stamp}.csv`, rows, 'text/csv;charset=utf-8'); } else { downloadTextFile(`${kbData.id}-${stamp}.json`, JSON.stringify({ knowledgeBase: kbData, files: includeFiles ? kbFiles : undefined, slices: includeFiles ? mockSlices : undefined, }, null, 2)); } Toast.success('知识库已导出'); } }); }; window.__deleteKb = function() { createModal({ title: '删除知识库', content: `

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

此操作不可恢复

关联应用将无法继续使用该知识库。

`, confirmText: '删除', confirmClass: 'btn-danger', onConfirm: () => { // TODO: 调用 API DELETE /api/v1/knowledge-bases/:id if (Array.isArray(window.__knowledgeBaseCatalog)) { const index = window.__knowledgeBaseCatalog.findIndex(item => item.id === kbData.id); if (index !== -1) window.__knowledgeBaseCatalog.splice(index, 1); } Toast.success('知识库已删除'); setTimeout(() => navigateTo('knowledge-base'), 1000); } }); lucide.createIcons(); }; // ==================== 初始化 ==================== setTimeout(() => { isLoading = false; renderPage(); }, 300); });