/**
* TalkingQ Platform - 创建知识库向导页面
* 包含3步流程:基础信息 -> 数据选择 -> 索引设置
*
* API预留:
* - POST /api/v1/knowledge-bases - 创建知识库
* - POST /api/v1/knowledge-bases/validate-name - 验证名称唯一性
* - POST /api/v1/files/upload - 文件上传
* - GET /api/v1/files/upload-status/:id - 获取上传状态
*/
registerPage('kb-create', function(container) {
// ==================== 状态管理 ====================
let currentStep = 1;
const totalSteps = 3;
let hasUnsavedChanges = false;
const DRAFT_KEY = 'kb_create_draft';
// ==================== API预留 - 后端接口定义 ====================
/**
* 创建知识库
* POST /api/v1/knowledge-bases
* Body: { name, description, type, sourceType, sourceConfig, indexConfig }
* Response: { id: string, name: string, status: string }
*/
const API_CREATE = '/api/v1/knowledge-bases';
/**
* 验证名称唯一性
* POST /api/v1/knowledge-bases/validate-name
* Body: { name: string }
* Response: { valid: boolean, message?: string }
*/
const API_VALIDATE_NAME = '/api/v1/knowledge-bases/validate-name';
/**
* 文件上传
* POST /api/v1/files/upload
* FormData: { file, category, knowledgeBaseId? }
* Response: { fileId: string, fileName: string, status: string }
*/
const API_UPLOAD = '/api/v1/files/upload';
/**
* 获取上传/解析状态
* GET /api/v1/files/status/:fileId
* Response: { status: 'uploading'|'processing'|'ready'|'error', progress?: number, error?: string }
*/
const API_FILE_STATUS = '/api/v1/files/status/:fileId';
/**
* 获取类目列表
* GET /api/v1/categories
* Response: { list: [{ id, name, fileCount }] }
*/
const API_GET_CATEGORIES = '/api/v1/categories';
// ==================== 表单数据 ====================
const formData = {
// 步骤1: 基础信息
name: '',
description: '',
type: '',
// 步骤2: 数据选择
sourceType: 'upload', // upload | category | files
category: '',
uploadedFiles: [],
autoSync: false,
parseMethod: 'inherit', // inherit | default | custom
// 自定义解析配置
docParseMethods: ['electronic'],
imageParseMethods: ['smart'],
videoParseMethods: ['media'],
enableVideoParse: false,
// 步骤3: 索引设置
sliceMethod: 'smart', // smart | length | page | title | regex | symbol
maxChunkLength: 2000,
overlapLength: 200,
titleLevel: 3,
regexPattern: '',
symbolChars: ',',
enableMetadataExtract: true,
enableExcelHeader: false,
enableMultiTurn: false,
enableOCR: false,
enableAutoKeywords: false,
enableAutoTags: false,
vectorModel: 'text-embedding-v2',
rerankModel: 'bge-reranker-v2.5',
rerankMode: 'qa', // qa | similarity | custom
vectorTopK: 60,
keywordTopK: 60,
similarityThreshold: 0.5,
maxRecallCount: 10,
vectorStorage: 'platform'
};
// ==================== 知识库类型定义 ====================
const kbTypes = [
{
id: 'doc_search',
icon: 'file-text',
title: '文档搜索',
desc: '构建文档、文件、图片、Excel混合型索引知识库',
tooltip: '构建文档、文件、图片、Excel混合型索引知识库,构建常规混合搜索知识库。支持PDF、Word、Excel、图片等多种格式文档。'
},
{
id: 'data_query',
icon: 'table',
title: '数据查询',
desc: '基于NL2SQL的表格数据查询',
tooltip: '构建仅以数据表结构查询的数据索引体系,基于NL2SQL思路,实现表头、列头等结构化数据查询能力。适合结构化数据问答场景。'
},
{
id: 'image_qa',
icon: 'image',
title: '图片问答',
desc: '基于多模态Embedding的图片检索',
tooltip: '构建仅以图片索引为主的知识库,基于多模态Embedding能力,实现图片名称与图片内容搜索。适合图片素材检索场景。'
},
{
id: 'video_search',
icon: 'video',
title: '音视频搜索',
desc: '音视频内容智能理解',
tooltip: '构建音视频搜索知识库,基于高精度解析与多模态Embedding检索技术,对音视频内容进行智能理解与提炼。支持字幕提取、内容摘要等。'
}
];
// ==================== 切片方式 ====================
const sliceMethods = [
{ id: 'smart', label: '智能切分', desc: '根据语义自动识别段落边界' },
{ id: 'length', label: '按长度切分', desc: '固定长度切分,支持重叠' },
{ id: 'page', label: '按页切分', desc: '按页分割文档' },
{ id: 'title', label: '按标题切分', desc: '按标题层级结构切分' },
{ id: 'regex', label: '按正则切分', desc: '自定义正则表达式切分' },
{ id: 'symbol', label: '按符号切分', desc: '按标点符号切分' }
];
// ==================== 符号选项 ====================
const symbolOptions = [
{ value: ',', label: '中文逗号 ,' },
{ value: '。', label: '中文句号 。' },
{ value: ',', label: '中文逗号(英文)' },
{ value: '.', label: '英文句号 .' },
{ value: '、', label: '顿号 、' },
{ value: '?', label: '中文问号 ?' },
{ value: '!', label: '中文感叹号 !' },
{ value: '?', label: '英文问号 ?' },
{ value: '!', label: '英文感叹号 !' },
{ value: '\n', label: '换行符' },
{ value: '\n\n', label: '双换行符' }
];
// ==================== 类目数据 ====================
const categories = [
{ id: 'cat-001', name: '默认类目', fileCount: 128 },
{ id: 'cat-002', name: '教学资料', fileCount: 56 },
{ id: 'cat-003', name: '产品文档', fileCount: 34 },
{ id: 'cat-004', name: '用户指南', fileCount: 89 },
];
// ==================== 工具函数 ====================
function escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
function getFormatsByType() {
switch (formData.type) {
case 'image_qa':
return '.png,.jpg,.jpeg,.bmp,.gif';
case 'video_search':
return '.mp4,.avi,.mov,.mkv,.wmv,.mp3,.wav,.m4a';
case 'data_query':
return '.xlsx,.xls,.csv';
default:
return '.pdf,.doc,.docx,.txt,.md,.ppt,.pptx,.xlsx,.xls,.png,.jpg,.jpeg,.gif';
}
}
function getFileIcon(filename) {
const ext = filename?.split('.').pop()?.toLowerCase() || '';
const icons = {
pdf: '📄', doc: '📄', docx: '📄', txt: '📝', md: '📋',
xlsx: '📊', xls: '📊', csv: '📊',
png: '🖼', jpg: '🖼', jpeg: '🖼', gif: '🖼', bmp: '🖼',
mp4: '🎬', avi: '🎬', mov: '🎬', mkv: '🎬', wmv: '🎬',
mp3: '🎵', wav: '🎵', m4a: '🎵'
};
return icons[ext] || '📁';
}
function getFileStatusText(status) {
const map = {
pending: '待上传',
uploading: '上传中',
processing: '解析中',
ready: '解析完成',
error: '解析失败'
};
return map[status] || status;
}
// ==================== 草稿管理 ====================
function loadDraft() {
try {
const draft = localStorage.getItem(DRAFT_KEY);
if (draft) {
const saved = JSON.parse(draft);
Object.assign(formData, saved.formData || {});
currentStep = saved.currentStep || 1;
return true;
}
} catch (e) {
console.error('Failed to load draft:', e);
}
return false;
}
function saveDraft() {
try {
localStorage.setItem(DRAFT_KEY, JSON.stringify({
formData,
currentStep,
savedAt: new Date().toISOString()
}));
Toast.success('草稿已保存');
} catch (e) {
Toast.warning('草稿保存失败');
}
}
function clearDraft() {
localStorage.removeItem(DRAFT_KEY);
}
// ==================== 验证函数 ====================
function validateStep1() {
const errors = [];
if (!formData.name.trim()) {
errors.push('请输入知识库名称');
} else if (formData.name.length < 2 || formData.name.length > 64) {
errors.push('知识库名称长度需在2-64字符之间');
}
if (!formData.type) {
errors.push('请选择知识库类型');
}
return errors;
}
function validateStep2() {
const errors = [];
if (formData.sourceType === 'upload' && formData.uploadedFiles.length === 0) {
errors.push('请上传至少一个文件');
}
if (formData.sourceType === 'upload') {
const failedFiles = formData.uploadedFiles.filter(f => f.status === 'error');
if (failedFiles.length > 0) {
errors.push(`有 ${failedFiles.length} 个文件上传失败,请处理后重试`);
}
}
return errors;
}
function validateStep3() {
return []; // 步骤3一般不需要强制验证
}
// ==================== 渲染函数 ====================
/**
* 渲染向导步骤
*/
function renderWizardSteps() {
const steps = ['基础信息', '数据选择', '索引设置'];
return `
${[1, 2, 3].map((step, idx) => `
${step < currentStep ? '' : step}
${steps[step - 1]}
${idx < 2 ? `
` : ''}
`).join('')}
`;
}
/**
* 渲染步骤1:基础信息
*/
function renderStep1() {
return `
`;
}
/**
* 渲染步骤2:数据选择
*/
function renderStep2() {
const formats = getFormatsByType();
const maxFiles = 50;
const maxSize = formData.type === 'image_qa' || formData.type === 'video_search' ? '20MB' : '50MB';
return `
选择数据源
默认文件连接器
从连接器内选择已解析文档构建知识索引,也支持直接上传文件解析并构建索引,上传文件将自动存储至连接器。
${renderSourceContent(formats, maxFiles, maxSize)}
${formData.sourceType !== 'upload' ? `
` : ''}
`;
}
/**
* 渲染数据源内容
*/
function renderSourceContent(formats, maxFiles, maxSize) {
switch (formData.sourceType) {
case 'upload':
return renderUploadContent(formats, maxFiles, maxSize);
case 'category':
return renderCategoryContent();
case 'files':
return renderFilesContent();
default:
return renderUploadContent(formats, maxFiles, maxSize);
}
}
/**
* 渲染文件上传内容
*/
function renderUploadContent(formats, maxFiles, maxSize) {
return `
配置类目:
${formData.uploadedFiles.length > 0 ? `
${formData.uploadedFiles.map((file, idx) => `
${getFileIcon(file.name)}
${escapeHtml(file.name)}
${file.size}
${file.status === 'uploading' ? `` : ''}
${file.status === 'processing' ? `` : ''}
${getFileStatusText(file.status)}
${file.status === 'error' ? `
` : ''}
`).join('')}
已选择 ${formData.uploadedFiles.length}/${maxFiles} 个文件
` : ''}
解析方式
?
${formData.parseMethod === 'custom' ? `
${renderParseMethodDetail()}
` : ''}
`;
}
/**
* 渲染自定义解析方式
*/
function renderParseMethodDetail() {
const type = formData.type;
let html = '自定义解析方式:
';
if (['doc_search', 'data_query', ''].includes(type)) {
html += `
文档类型
电子文档解析
Markdown解析
HTML解析
PPT文档智能解析
大模型文档解析
`;
}
if (type === 'image_qa' || !type) {
html += `
`;
}
if (type === 'video_search' || !type) {
html += `
`;
}
return html;
}
/**
* 渲染类目选择内容
*/
function renderCategoryContent() {
return `
选择类目:
选择类目后,该类目下所有文件将被导入并构建索引。
当前类目包含 128 个文件,预计解析时间约 5-10 分钟。
`;
}
/**
* 渲染文件选择内容
*/
function renderFilesContent() {
const appFiles = [
{ name: '产品说明书.pdf', size: '3.2MB', format: 'PDF' },
{ name: '用户指南.docx', size: '1.5MB', format: 'DOCX' },
{ name: '技术规格.xlsx', size: '856KB', format: 'XLSX' },
{ name: '培训视频.mp4', size: '128MB', format: 'MP4' },
{ name: '常见问题.md', size: '245KB', format: 'MD' },
];
return `
应用数据类目:
${appFiles.map((f, idx) => `
${getFileIcon(f.name)}
${f.name}
${f.format} · ${f.size}
`).join('')}
`;
}
/**
* 渲染步骤3:索引设置
*/
function renderStep3() {
return `
索引设置
?
${renderSliceConfig()}
多轮对话改写
OCR增强
自动关键词提取
自动标签提取
系统会自动保存草稿,您可以随时中断并继续
`;
}
/**
* 渲染切片配置
*/
function renderSliceConfig() {
switch (formData.sliceMethod) {
case 'smart':
return `
`;
case 'length':
return `
`;
case 'page':
return `
`;
case 'title':
return `
`;
case 'regex':
return `
`;
case 'symbol':
return `
符号选择器
`;
default:
return '';
}
}
// ==================== 渲染页面 ====================
function renderPage() {
container.innerHTML = `
${renderWizardSteps()}
${currentStep === 1 ? renderStep1() : currentStep === 2 ? renderStep2() : renderStep3()}
`;
lucide.createIcons({ elements: [container] });
}
// ==================== 步骤控制 ====================
function collectStepData() {
if (currentStep === 1) {
formData.name = document.getElementById('kbName')?.value.trim() || '';
formData.description = document.getElementById('kbDesc')?.value.trim() || '';
}
}
window.__prevStep = function() {
if (currentStep > 1) {
collectStepData();
currentStep--;
renderPage();
}
};
window.__nextStep = function() {
collectStepData();
let errors = [];
if (currentStep === 1) {
errors = validateStep1();
} else if (currentStep === 2) {
errors = validateStep2();
}
if (errors.length > 0) {
Toast.error(errors[0]);
return;
}
if (currentStep < totalSteps) {
currentStep++;
renderPage();
}
};
window.__finishWizard = function() {
collectStepData();
// TODO: 调用 API POST /api/v1/knowledge-bases 创建知识库
console.log('创建知识库数据:', formData);
const typeMap = {
doc_search: '文档搜索',
data_query: '数据查询',
image_qa: '图片问答',
video_search: '音视频搜索',
};
const createdAt = new Date();
const pad = value => String(value).padStart(2, '0');
const createdText = `${createdAt.getFullYear()}-${pad(createdAt.getMonth() + 1)}-${pad(createdAt.getDate())} ${pad(createdAt.getHours())}:${pad(createdAt.getMinutes())}:${pad(createdAt.getSeconds())}`;
const uploadedCount = Array.isArray(formData.uploadedFiles) ? formData.uploadedFiles.length : 0;
const newKb = {
id: `kb-${Date.now()}`,
name: formData.name || '未命名知识库',
description: formData.description || '暂无描述',
docs: uploadedCount * 24,
size: uploadedCount ? `${Math.max(1, uploadedCount * 8)}MB` : '0MB',
type: formData.type || 'doc_search',
typeName: typeMap[formData.type] || '文档搜索',
status: uploadedCount ? 'processing' : 'ready',
createdAt: createdText,
updatedAt: createdText,
tags: [],
};
const catalog = Array.isArray(window.__knowledgeBaseCatalog)
? window.__knowledgeBaseCatalog
: [];
catalog.unshift(newKb);
window.__knowledgeBaseCatalog = catalog;
clearDraft();
Toast.success('知识库创建成功!');
setTimeout(() => {
navigateTo('knowledge-base');
}, 1000);
};
// ==================== 全局函数 ====================
window.__updateCharCount = function(input, max) {
const counter = input.closest('.form-group')?.querySelector('.char-counter');
if (counter) {
counter.textContent = `${input.value.length}/${max}`;
counter.classList.toggle('warning', input.value.length >= max * 0.9);
}
hasUnsavedChanges = true;
};
window.__selectKbType = function(typeId) {
formData.type = typeId;
hasUnsavedChanges = true;
document.querySelectorAll('.kb-type-card').forEach(card => {
card.classList.toggle('selected', card.dataset.type === typeId);
});
};
window.__showKbTypeHelp = function() {
createModal({
title: '知识库类型说明',
content: `
${kbTypes.map(type => `
`).join('')}
`,
confirmText: '',
cancelText: '关闭'
});
lucide.createIcons();
};
window.__selectSourceType = function(type) {
formData.sourceType = type;
hasUnsavedChanges = true;
const formats = getFormatsByType();
const maxFiles = 50;
const maxSize = formData.type === 'image_qa' || formData.type === 'video_search' ? '20MB' : '50MB';
document.getElementById('sourceContent').innerHTML = renderSourceContent(formats, maxFiles, maxSize);
lucide.createIcons();
};
window.__handleDragOver = function(e) {
e.preventDefault();
e.currentTarget.classList.add('dragover');
};
window.__handleDragLeave = function(e) {
e.currentTarget.classList.remove('dragover');
};
window.__handleDrop = function(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
window.__handleFileSelect(e.dataTransfer.files);
};
window.__handleFileSelect = function(files) {
if (!files || !files.length) return;
const maxFiles = 50;
const maxSize = formData.type === 'image_qa' || formData.type === 'video_search' ? 20 * 1024 * 1024 : 50 * 1024 * 1024;
Array.from(files).forEach(file => {
if (formData.uploadedFiles.length >= maxFiles) {
Toast.warning(`最多只能上传${maxFiles}个文件`);
return;
}
if (file.size > maxSize) {
Toast.warning(`文件 ${file.name} 超过大小限制 (${maxSize / 1024 / 1024}MB)`);
return;
}
const fileObj = {
name: file.name,
size: (file.size / 1024 / 1024).toFixed(2) + 'MB',
status: 'uploading',
file: file
};
formData.uploadedFiles.push(fileObj);
hasUnsavedChanges = true;
// TODO: 调用 API POST /api/v1/files/upload 上传文件
// 模拟上传进度
setTimeout(() => {
fileObj.status = 'processing';
renderPage();
}, 500);
setTimeout(() => {
// 模拟90%成功率
fileObj.status = Math.random() > 0.1 ? 'ready' : 'error';
renderPage();
}, 2000);
});
renderPage();
};
window.__removeFile = function(index) {
formData.uploadedFiles.splice(index, 1);
hasUnsavedChanges = true;
renderPage();
};
window.__retryUpload = function(index) {
if (formData.uploadedFiles[index]) {
const fileObj = formData.uploadedFiles[index];
fileObj.status = 'uploading';
renderPage();
setTimeout(() => {
fileObj.status = 'processing';
renderPage();
}, 500);
setTimeout(() => {
fileObj.status = 'ready';
renderPage();
Toast.success(`${fileObj.name} 已重新上传并解析完成`);
}, 1500);
}
};
window.__clearAllFiles = function() {
formData.uploadedFiles = [];
hasUnsavedChanges = true;
renderPage();
};
window.__updateCategory = function(value) {
formData.category = value;
hasUnsavedChanges = true;
};
window.__selectParseMethod = function(method) {
formData.parseMethod = method;
hasUnsavedChanges = true;
document.querySelectorAll('.parse-method-option').forEach(opt => {
const isSelected = opt.textContent.includes(
method === 'inherit' ? '继承' : method === 'default' ? '默认' : '自定义'
);
opt.classList.toggle('selected', isSelected);
});
const detail = document.getElementById('parseMethodDetail');
if (detail) {
detail.innerHTML = renderParseMethodDetail();
}
};
window.__toggleParseMethod = function(type, method) {
const key = type === 'doc' ? 'docParseMethods' : type === 'image' ? 'imageParseMethods' : 'videoParseMethods';
const arr = formData[key];
const idx = arr.indexOf(method);
if (idx > -1) {
arr.splice(idx, 1);
} else {
arr.push(method);
}
hasUnsavedChanges = true;
const detail = document.getElementById('parseMethodDetail');
if (detail) {
detail.innerHTML = renderParseMethodDetail();
lucide.createIcons({ elements: [detail] });
}
};
window.__toggleAutoSync = function(checked) {
formData.autoSync = checked;
hasUnsavedChanges = true;
Toast.info(checked ? '已开启自动同步' : '已关闭自动同步');
};
window.__updateSliceConfig = function() {
formData.sliceMethod = document.getElementById('sliceMethod').value;
hasUnsavedChanges = true;
document.getElementById('sliceConfigPanel').innerHTML = renderSliceConfig();
lucide.createIcons();
};
window.__updateRangeValue = function(input, field, multiplier = 1) {
const value = parseInt(input.value);
const displayEl = document.getElementById(field + 'Value');
if (displayEl) {
displayEl.textContent = multiplier > 1 ? (value / multiplier).toFixed(2) : value;
}
formData[field] = multiplier > 1 ? value / multiplier : value;
hasUnsavedChanges = true;
};
window.__resetRetrievalParams = function() {
formData.vectorTopK = 60;
formData.keywordTopK = 60;
formData.similarityThreshold = 0.5;
formData.maxRecallCount = 10;
hasUnsavedChanges = true;
renderPage();
};
window.__toggleEnhance = function(key) {
formData['enable' + key.charAt(0).toUpperCase() + key.slice(1)] = !formData['enable' + key.charAt(0).toUpperCase() + key.slice(1)];
hasUnsavedChanges = true;
const advancedBody = document.querySelector('.advanced-config-body');
if (advancedBody) {
const options = advancedBody.querySelectorAll('.parse-method-option');
options.forEach(opt => {
if (opt.textContent.includes('Metadata抽取')) {
opt.classList.toggle('selected', formData.enableMetadataExtract);
} else if (opt.textContent.includes('Excel表头')) {
opt.classList.toggle('selected', formData.enableExcelHeader);
} else if (opt.textContent.includes('多轮对话')) {
opt.classList.toggle('selected', formData.enableMultiTurn);
} else if (opt.textContent.includes('OCR')) {
opt.classList.toggle('selected', formData.enableOCR);
} else if (opt.textContent.includes('关键词')) {
opt.classList.toggle('selected', formData.enableAutoKeywords);
} else if (opt.textContent.includes('标签')) {
opt.classList.toggle('selected', formData.enableAutoTags);
}
});
}
};
window.__showTooltip = function(id) {
const tooltip = document.getElementById(id);
if (tooltip) {
tooltip.style.display = tooltip.style.display === 'none' ? 'block' : 'none';
}
};
window.__toggleFileSelect = function(el, filename) {
const checkbox = el.querySelector('input[type="checkbox"]');
checkbox.checked = !checkbox.checked;
el.classList.toggle('selected', checkbox.checked);
Toast.info(checkbox.checked ? `已选择: ${filename}` : `已取消: ${filename}`);
};
window.__loadCategoryFiles = function(category) {
// TODO: 调用 API GET /api/v1/categories/:id/files
createModal({
title: `${category} 文件列表`,
size: 'lg',
content: `
${['儿童科学百科.pdf', '科学小实验.docx', '物理入门.xlsx'].map((name, index) => `
${name}
mock-file-${index + 1} · ${(index + 1) * 2}.4MB
`).join('')}
`,
confirmText: '',
cancelText: '关闭'
});
};
window.__showCategoryDrawer = function() {
const renderCategoryRows = () => {
const list = document.getElementById('categoryManageList');
if (!list) return;
list.innerHTML = categories.map(category => `
${category.name}
${category.fileCount} 个文件 · ${category.id}
${category.id === 'cat-001' ? '' : `
`}
`).join('');
lucide.createIcons({ elements: list });
};
createModal({
title: '类目管理',
size: 'lg',
content: `
`,
confirmText: '',
cancelText: '关闭'
});
window.__addCategoryFromModal = function() {
const input = document.getElementById('newCategoryName');
const name = input?.value.trim();
if (!name) {
Toast.warning('请输入类目名称');
return;
}
categories.push({
id: `cat-${Date.now()}`,
name,
fileCount: 0,
});
input.value = '';
renderCategoryRows();
Toast.success('类目已添加');
};
window.__removeCategoryFromModal = function(id) {
const index = categories.findIndex(category => category.id === id);
if (index !== -1) {
categories.splice(index, 1);
renderCategoryRows();
Toast.success('类目已删除');
}
};
window.__chooseCategoryFromModal = function(name) {
formData.category = name;
hasUnsavedChanges = true;
const select = document.getElementById('categorySelect');
if (select && Array.from(select.options).some(option => option.value === name)) {
select.value = name;
}
Toast.success(`已选择类目:${name}`);
};
setTimeout(renderCategoryRows, 50);
};
window.__saveDraft = saveDraft;
window.__confirmLeave = function() {
if (hasUnsavedChanges) {
createModal({
title: '确认离开',
content: `
`,
confirmText: '离开',
confirmClass: 'btn-danger',
onConfirm: () => {
navigateTo('knowledge-base');
}
});
} else {
navigateTo('knowledge-base');
}
};
// ==================== 初始化 ====================
loadDraft();
renderPage();
// 离开页面确认
window.addEventListener('beforeunload', function(e) {
if (hasUnsavedChanges) {
e.preventDefault();
e.returnValue = '';
}
});
});