|
|
|
|
@ -426,53 +426,52 @@
|
|
|
|
|
|
|
|
|
|
<!-- 主内容区 -->
|
|
|
|
|
<main class="flex-1 overflow-y-auto bg-gray-50">
|
|
|
|
|
<!-- 笔记管理视图(默认显示) -->
|
|
|
|
|
<div id="notes-view" class="view-container flex flex-col h-full"> <!-- 用flex-col确保内部模块垂直排列 -->
|
|
|
|
|
<!-- 工具栏 -->
|
|
|
|
|
<div class="bg-white p-4 border-b border-gray-200 flex items-center justify-between">
|
|
|
|
|
<h1 class="text-xl font-semibold">笔记管理</h1>
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
|
|
|
|
|
<div class="relative mr-3">
|
|
|
|
|
<select id="categoryFilter" class="input-field pl-3 pr-8 w-48 border border-gray-300 rounded-md py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 appearance-none bg-white">
|
|
|
|
|
<!-- 选项将通过JS动态生成 -->
|
|
|
|
|
</select>
|
|
|
|
|
<i class="fa fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"></i>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 笔记管理视图(默认显示) -->
|
|
|
|
|
<div id="notes-view" class="view-container flex flex-col h-full">
|
|
|
|
|
<!-- 工具栏 -->
|
|
|
|
|
<div class="bg-white p-4 border-b border-gray-200 flex items-center justify-between">
|
|
|
|
|
<h1 class="text-xl font-semibold">笔记管理</h1>
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
<div class="relative mr-3">
|
|
|
|
|
<select id="categoryFilter" class="input-field pl-3 pr-8 w-48 border border-gray-300 rounded-md py-2 focus:outline-none focus:ring-2 focus:ring-primary/50 appearance-none bg-white">
|
|
|
|
|
<!-- 选项将通过JS动态生成 -->
|
|
|
|
|
</select>
|
|
|
|
|
<i class="fa fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="relative">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="noteSearch"
|
|
|
|
|
placeholder="搜索笔记..."
|
|
|
|
|
class="input-field pl-9 w-64 border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn-action btn-primary bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors" id="newNoteBtn">
|
|
|
|
|
<i class="fa fa-plus mr-1"></i> 新建笔记
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn-action btn-secondary border border-gray-300 px-4 py-2 rounded-md hover:bg-gray-100 transition-colors" id="importNoteBtn">
|
|
|
|
|
<i class="fa fa-upload mr-1"></i> 导入
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="relative">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="noteSearch"
|
|
|
|
|
placeholder="搜索笔记..."
|
|
|
|
|
class="input-field pl-9 w-64 border border-gray-300 rounded-md py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn-action btn-primary bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors" id="newNoteBtn">
|
|
|
|
|
<i class="fa fa-plus mr-1"></i> 新建笔记
|
|
|
|
|
</button>
|
|
|
|
|
<button class="btn-action btn-secondary border border-gray-300 px-4 py-2 rounded-md hover:bg-gray-100 transition-colors" id="importNoteBtn">
|
|
|
|
|
<i class="fa fa-upload mr-1"></i> 导入
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 已上传文件列表 -->
|
|
|
|
|
<div class="p-6 border-b border-gray-200 bg-white">
|
|
|
|
|
<h2 class="text-lg font-semibold mb-4">已上传文件</h2>
|
|
|
|
|
<div id="documents-container" class="border rounded-lg p-4 bg-gray-50">
|
|
|
|
|
<!-- 文件列表将通过JS动态生成 -->
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 笔记列表区域 -->
|
|
|
|
|
<div class="p-6 flex-1"> <!-- flex-1占满剩余空间,避免内容过短 -->
|
|
|
|
|
<h2 class="text-lg font-semibold mb-4">我的笔记</h2> <!-- 补充标题区分模块 -->
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-11" id="notes-container">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 笔记列表区域(删除了"我的笔记"标题) -->
|
|
|
|
|
<div class="p-6 flex-1">
|
|
|
|
|
<!-- 删除了原来的 <h2 class="text-lg font-semibold mb-4">我的笔记</h2> -->
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="notes-container">
|
|
|
|
|
<!-- 笔记卡片将通过JS动态生成 -->
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 已上传文件列表(移到笔记列表下方,作为独立模块) -->
|
|
|
|
|
<div class="p-6 border-t border-gray-200 bg-white"> <!-- 用白色背景区分 -->
|
|
|
|
|
<h2 class="text-lg font-semibold mb-4">已上传文件</h2>
|
|
|
|
|
<div id="documents-container" class="border rounded-lg p-4 bg-gray-50">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 智能答疑视图(默认隐藏) -->
|
|
|
|
|
<div id="qa-view" class="view-container hidden flex flex-col h-full"> <!-- 用flex-col垂直排列聊天区和输入区 -->
|
|
|
|
|
@ -565,19 +564,21 @@
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label for="graphNoteIds" class="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
文档ID <span class="text-red-500">*</span>
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="graphNoteIds"
|
|
|
|
|
class="input-field w-full"
|
|
|
|
|
placeholder="多个用逗号分隔(如:file1.txt,file2.docx)"
|
|
|
|
|
required
|
|
|
|
|
>
|
|
|
|
|
<p class="text-xs text-gray-500 mt-1">从已上传的文档中选择ID</p>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 替换原来的文档ID输入框 -->
|
|
|
|
|
<div>
|
|
|
|
|
<label for="graphCategory" class="block text-sm font-medium text-gray-700 mb-1">
|
|
|
|
|
分类 <span class="text-red-500">*</span>
|
|
|
|
|
</label>
|
|
|
|
|
<select
|
|
|
|
|
id="graphCategory"
|
|
|
|
|
class="input-field w-full"
|
|
|
|
|
required
|
|
|
|
|
>
|
|
|
|
|
<option value="">请选择分类</option>
|
|
|
|
|
<!-- 动态生成分类选项 -->
|
|
|
|
|
</select>
|
|
|
|
|
<p class="text-xs text-gray-500 mt-1">选择分类后,将生成该分类下所有文件的知识图谱</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex justify-end pt-1">
|
|
|
|
|
@ -871,15 +872,17 @@
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 修改后(正确) -->
|
|
|
|
|
<!-- 修改导入笔记模态框中的分类输入框,使用唯一ID -->
|
|
|
|
|
<div>
|
|
|
|
|
<label for="importModalCategory" class="block text-sm font-medium text-gray-700 mb-1">笔记分类</label>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
id="noteCategory"
|
|
|
|
|
id="importModalCategory"
|
|
|
|
|
class="login-input"
|
|
|
|
|
placeholder="请输入笔记分类(例如:高等数学、计算机网络)"
|
|
|
|
|
required
|
|
|
|
|
>
|
|
|
|
|
<p id="importCategoryError" class="text-red-500 text-xs mt-1 hidden">请输入笔记分类</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 按钮 -->
|
|
|
|
|
@ -922,7 +925,7 @@
|
|
|
|
|
// DOM元素
|
|
|
|
|
let conversationHistory = [];
|
|
|
|
|
// 基础地址只保留 域名/IP + 端口
|
|
|
|
|
const API_BASE_URL = 'http://192.168.54.16:9621';
|
|
|
|
|
const API_BASE_URL = 'http://192.168.131.16:9621';
|
|
|
|
|
// 调用接口时,再拼接具体路径(如上传接口)
|
|
|
|
|
const uploadUrl = `${API_BASE_URL}/documents/upload`;
|
|
|
|
|
const splashScreen = document.getElementById('splashScreen');
|
|
|
|
|
@ -1769,14 +1772,22 @@ function closeImportModalHandler() {
|
|
|
|
|
}, 300);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重置导入表单
|
|
|
|
|
// 重置导入表单(修正版)
|
|
|
|
|
function resetImportForm() {
|
|
|
|
|
importNoteForm.reset();
|
|
|
|
|
if (importNoteForm) {
|
|
|
|
|
importNoteForm.reset();
|
|
|
|
|
}
|
|
|
|
|
importFile.value = '';
|
|
|
|
|
selectedFileInfo.classList.add('hidden');
|
|
|
|
|
fileName.textContent = '';
|
|
|
|
|
importedFileContent = '';
|
|
|
|
|
currentParsingFile = null;
|
|
|
|
|
|
|
|
|
|
// 隐藏分类错误提示
|
|
|
|
|
const categoryError = document.getElementById('importCategoryError');
|
|
|
|
|
if (categoryError) {
|
|
|
|
|
categoryError.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文件上传区域事件
|
|
|
|
|
@ -1866,6 +1877,10 @@ function handleFileSelection(file) {
|
|
|
|
|
fileName.textContent = file.name;
|
|
|
|
|
selectedFileInfo.classList.remove('hidden');
|
|
|
|
|
fetchAllDocuments(); // 新增:上传成功后刷新文件列表
|
|
|
|
|
// 新增:更新知识图谱分类选择框
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
renderGraphCategoryFilter();
|
|
|
|
|
}, 500);
|
|
|
|
|
} else if (data.status === 'duplicated') {
|
|
|
|
|
// 处理文件重复的情况
|
|
|
|
|
alert(`文件已存在:${data.message}`);
|
|
|
|
|
@ -2118,7 +2133,7 @@ removeFile.addEventListener('click', () => {
|
|
|
|
|
currentParsingFile = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 导入笔记表单提交(适配后端异步处理)
|
|
|
|
|
// 导入笔记表单提交(修正版)
|
|
|
|
|
importNoteForm.addEventListener('submit', (e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
|
|
@ -2127,24 +2142,85 @@ importNoteForm.addEventListener('submit', (e) => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示进度提示(后端异步处理,这里提示用户等待)
|
|
|
|
|
// 获取分类信息 - 使用正确的ID
|
|
|
|
|
const categoryInput = document.getElementById('importModalCategory');
|
|
|
|
|
const category = categoryInput.value.trim();
|
|
|
|
|
|
|
|
|
|
// 验证分类是否填写
|
|
|
|
|
if (!category) {
|
|
|
|
|
const errorElement = document.getElementById('importCategoryError');
|
|
|
|
|
if (errorElement) {
|
|
|
|
|
errorElement.classList.remove('hidden');
|
|
|
|
|
} else {
|
|
|
|
|
alert('请填写笔记分类!');
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 隐藏错误提示
|
|
|
|
|
const errorElement = document.getElementById('importCategoryError');
|
|
|
|
|
if (errorElement) {
|
|
|
|
|
errorElement.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示进度提示
|
|
|
|
|
importNoteModal.classList.add('hidden');
|
|
|
|
|
importProgressModal.classList.remove('hidden');
|
|
|
|
|
importProgressBar.style.width = '50%';
|
|
|
|
|
importProgressText.textContent = '文件已上传,等待服务器处理中...';
|
|
|
|
|
|
|
|
|
|
// 实际项目中,这里应该轮询后端接口查询处理状态
|
|
|
|
|
// 这里简化为3秒后模拟处理完成
|
|
|
|
|
// 保存分类信息
|
|
|
|
|
const fileName = currentParsingFile.name;
|
|
|
|
|
saveFileCategoryMapping(fileName, category);
|
|
|
|
|
|
|
|
|
|
// 模拟处理完成(实际项目中这里应该是真实的API调用)
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
importProgressModal.classList.add('hidden');
|
|
|
|
|
alert('文件导入成功,已添加到笔记列表!');
|
|
|
|
|
alert(`文件"${fileName}"导入成功,分类为"${category}"!`);
|
|
|
|
|
resetImportForm();
|
|
|
|
|
fetchNotesFromServer(); // 刷新笔记数据
|
|
|
|
|
renderDynamicCategoryFilter(); // 更新分类
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
fetchAllDocuments(); // 刷新文件列表
|
|
|
|
|
|
|
|
|
|
// 更新知识图谱分类选择框
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
renderGraphCategoryFilter();
|
|
|
|
|
}, 500);
|
|
|
|
|
}, 2000);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 增强的文件分类映射管理函数
|
|
|
|
|
function saveFileCategoryMapping(fileName, category) {
|
|
|
|
|
try {
|
|
|
|
|
let fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
|
|
|
|
|
fileCategories[fileName] = category;
|
|
|
|
|
localStorage.setItem('fileCategories', JSON.stringify(fileCategories));
|
|
|
|
|
console.log(`分类映射已保存: ${fileName} -> ${category}`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('保存分类映射失败:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getFileCategory(fileName) {
|
|
|
|
|
try {
|
|
|
|
|
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
|
|
|
|
|
return fileCategories[fileName] || '未分类';
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取分类失败:', error);
|
|
|
|
|
return '未分类';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取所有分类列表
|
|
|
|
|
function getAllCategories() {
|
|
|
|
|
try {
|
|
|
|
|
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
|
|
|
|
|
const categories = new Set(Object.values(fileCategories).filter(cat => cat && cat.trim() !== ''));
|
|
|
|
|
return Array.from(categories).sort((a, b) => a.localeCompare(b));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取分类列表失败:', error);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 新增:从后端拉取最新笔记列表
|
|
|
|
|
function fetchNotesFromServer() {
|
|
|
|
|
const apiKey = '';
|
|
|
|
|
@ -2207,9 +2283,9 @@ function fetchAllDocuments() {
|
|
|
|
|
|
|
|
|
|
// 渲染文档列表(适配后端文档字段)
|
|
|
|
|
function renderDocumentsList() {
|
|
|
|
|
const container = document.getElementById('documents-container'); // 前端文档列表容器ID
|
|
|
|
|
const container = document.getElementById('documents-container');
|
|
|
|
|
if (!container) {
|
|
|
|
|
console.error('未找到文档列表容器,请检查DOM中是否有 id="documents-container" 的元素');
|
|
|
|
|
console.error('未找到文档列表容器');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -2218,58 +2294,68 @@ function renderDocumentsList() {
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
<div class="text-center py-10 text-gray-500">
|
|
|
|
|
<i class="fa fa-file-text-o text-3xl mb-3"></i>
|
|
|
|
|
<p>暂无上传的文档,请点击"上传文件"按钮开始操作</p>
|
|
|
|
|
<p>暂无上传的文档,请点击"导入"按钮开始上传文件</p>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 渲染文档表格(修正操作列的HTML语法)
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
<div class="overflow-x-auto">
|
|
|
|
|
<table class="min-w-full border-collapse border border-gray-200">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr class="bg-gray-50">
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">文件名</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">状态</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">上传时间</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">处理进度</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">操作</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
${window.allDocuments.map(doc => `
|
|
|
|
|
<tr class="hover:bg-gray-50">
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-900 border">${doc.file_path.split('/').pop()}</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm border">
|
|
|
|
|
<span class="px-2 py-1 rounded text-xs ${
|
|
|
|
|
doc.status === 'PROCESSED' ? 'bg-green-100 text-green-800' :
|
|
|
|
|
doc.status === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
|
|
|
|
|
doc.status === 'PROCESSING' ? 'bg-blue-100 text-blue-800' :
|
|
|
|
|
'bg-red-100 text-red-800'
|
|
|
|
|
}">${doc.status}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-600 border">${new Date(doc.created_at).toLocaleString()}</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-600 border">${
|
|
|
|
|
doc.status === 'PROCESSED' ? `已完成(${doc.chunks_count || 0}个片段)` :
|
|
|
|
|
doc.status === 'PROCESSING' ? '处理中...' :
|
|
|
|
|
doc.status === 'FAILED' ? `失败:${doc.error || '未知错误'}` :
|
|
|
|
|
'等待处理'
|
|
|
|
|
}</td>
|
|
|
|
|
<!-- 修正操作列:确保HTML标签闭合和属性正确 -->
|
|
|
|
|
<td class="px-4 py-3 text-sm border">
|
|
|
|
|
<button class="delete-document-btn text-red-500 hover:text-red-700 flex items-center gap-1 px-2 py-1 transition-colors"
|
|
|
|
|
data-doc-id="${doc.id}"
|
|
|
|
|
title="删除文件">
|
|
|
|
|
<i class="fa fa-trash-o"></i>
|
|
|
|
|
<span>删除</span>
|
|
|
|
|
</button>
|
|
|
|
|
</td>
|
|
|
|
|
|
|
|
|
|
// 获取文件分类映射
|
|
|
|
|
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
|
|
|
|
|
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
<div class="overflow-x-auto">
|
|
|
|
|
<table class="min-w-full border-collapse border border-gray-200">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr class="bg-gray-50">
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">文件名</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">分类</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">状态</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">上传时间</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">处理进度</th>
|
|
|
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-700 border">操作</th>
|
|
|
|
|
</tr>
|
|
|
|
|
`).join('')}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
${window.allDocuments.map(doc => {
|
|
|
|
|
const fileName = doc.file_path.split('/').pop();
|
|
|
|
|
const category = fileCategories[fileName] || '未分类';
|
|
|
|
|
return `
|
|
|
|
|
<tr class="hover:bg-gray-50">
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-900 border">${fileName}</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-600 border">
|
|
|
|
|
<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs">${category}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm border">
|
|
|
|
|
<span class="px-2 py-1 rounded text-xs ${
|
|
|
|
|
doc.status === 'PROCESSED' ? 'bg-green-100 text-green-800' :
|
|
|
|
|
doc.status === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
|
|
|
|
|
doc.status === 'PROCESSING' ? 'bg-blue-100 text-blue-800' :
|
|
|
|
|
'bg-red-100 text-red-800'
|
|
|
|
|
}">${doc.status}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-600 border">${new Date(doc.created_at).toLocaleString()}</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm text-gray-600 border">${
|
|
|
|
|
doc.status === 'PROCESSED' ? `已完成(${doc.chunks_count || 0}个片段)` :
|
|
|
|
|
doc.status === 'PROCESSING' ? '处理中...' :
|
|
|
|
|
doc.status === 'FAILED' ? `失败:${doc.error || '未知错误'}` :
|
|
|
|
|
'等待处理'
|
|
|
|
|
}</td>
|
|
|
|
|
<td class="px-4 py-3 text-sm border">
|
|
|
|
|
<button class="delete-document-btn text-red-500 hover:text-red-700 flex items-center gap-1 px-2 py-1 transition-colors"
|
|
|
|
|
data-doc-id="${doc.id}"
|
|
|
|
|
title="删除文件">
|
|
|
|
|
<i class="fa fa-trash-o"></i>
|
|
|
|
|
<span>删除</span>
|
|
|
|
|
</button>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
`;
|
|
|
|
|
}).join('')}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// 文档删除功能(使用事件委托,支持动态生成的按钮)
|
|
|
|
|
document.getElementById('documents-container').addEventListener('click', async function(e) {
|
|
|
|
|
@ -2295,7 +2381,7 @@ document.getElementById('documents-container').addEventListener('click', async f
|
|
|
|
|
try {
|
|
|
|
|
// 处理API密钥参数(后端要求的query参数)
|
|
|
|
|
const apiKey = ''; // 替换为实际API密钥,若无则留空
|
|
|
|
|
const deleteUrl = new URL('http://192.168.54.16:9621/documents/delete_document');
|
|
|
|
|
const deleteUrl = new URL('http://192.168.131.16:9621/documents/delete_document');
|
|
|
|
|
deleteUrl.searchParams.append('api_key_header_value', apiKey);
|
|
|
|
|
|
|
|
|
|
// 调用后端删除接口
|
|
|
|
|
@ -2688,13 +2774,14 @@ function formatMessageContent(content) {
|
|
|
|
|
.replace(/\n/g, '<br>') // 换行转<br>
|
|
|
|
|
.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" class="text-primary underline">$1</a>'); // 链接转超链接
|
|
|
|
|
}
|
|
|
|
|
// 初始化页面
|
|
|
|
|
// 在DOMContentLoaded和文件上传成功后调用
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
fetchNotesFromServer();
|
|
|
|
|
fetchAllDocuments();
|
|
|
|
|
document.getElementById('notes-view').classList.remove('hidden');
|
|
|
|
|
document.getElementById('qa-view').classList.add('hidden');
|
|
|
|
|
renderDynamicCategoryFilter(); // 初始化分类下拉框
|
|
|
|
|
renderDynamicCategoryFilter();
|
|
|
|
|
renderGraphCategoryFilter(); // 新增:初始化知识图谱分类选择框
|
|
|
|
|
const searchTerm = noteSearchInput.value.trim().toLowerCase();
|
|
|
|
|
filterNotesByCategoryAndSearch(searchTerm);
|
|
|
|
|
});
|
|
|
|
|
@ -2732,214 +2819,256 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
|
|
|
|
// 知识图谱功能实现
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
// 视图切换逻辑(已有的视图切换代码无需修改,会自动识别新视图)
|
|
|
|
|
|
|
|
|
|
// 知识图谱表单提交处理
|
|
|
|
|
const graphForm = document.getElementById('knowledgeGraphForm');
|
|
|
|
|
if (graphForm) {
|
|
|
|
|
graphForm.addEventListener('submit', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const nodeInfo = document.getElementById('node-info');
|
|
|
|
|
nodeInfo.className = 'loading-message';
|
|
|
|
|
nodeInfo.innerHTML = '正在加载知识图谱...';
|
|
|
|
|
|
|
|
|
|
// 1. 收集表单参数
|
|
|
|
|
const params = {
|
|
|
|
|
label: document.getElementById('graphLabel').value.trim(),
|
|
|
|
|
max_depth: document.getElementById('graphMaxDepth').value.trim() || 3,
|
|
|
|
|
max_nodes: document.getElementById('graphMaxNodes').value.trim() || 100,
|
|
|
|
|
note_ids: document.getElementById('graphNoteIds').value.trim().split(',').map(id => id.trim()).filter(Boolean)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 表单验证
|
|
|
|
|
if (!params.label || params.note_ids.length === 0) {
|
|
|
|
|
nodeInfo.className = 'error-message';
|
|
|
|
|
nodeInfo.innerHTML = '请填写必填参数(标签和文档ID)';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const importCategoryInput = document.getElementById('importModalCategory');
|
|
|
|
|
if (importCategoryInput) {
|
|
|
|
|
importCategoryInput.addEventListener('input', function() {
|
|
|
|
|
const errorElement = document.getElementById('importCategoryError');
|
|
|
|
|
if (errorElement && this.value.trim()) {
|
|
|
|
|
errorElement.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 知识图谱表单提交处理
|
|
|
|
|
const graphForm = document.getElementById('knowledgeGraphForm');
|
|
|
|
|
if (graphForm) {
|
|
|
|
|
graphForm.addEventListener('submit', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
const nodeInfo = document.getElementById('node-info');
|
|
|
|
|
nodeInfo.className = 'loading-message';
|
|
|
|
|
nodeInfo.innerHTML = '正在加载知识图谱...';
|
|
|
|
|
|
|
|
|
|
// 1. 收集表单参数
|
|
|
|
|
const selectedCategory = document.getElementById('graphCategory').value;
|
|
|
|
|
if (!selectedCategory) {
|
|
|
|
|
nodeInfo.className = 'error-message';
|
|
|
|
|
nodeInfo.innerHTML = '请选择分类';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 构建请求URL
|
|
|
|
|
const baseUrl = `${API_BASE_URL}/graphs/note`;
|
|
|
|
|
const urlParams = new URLSearchParams();
|
|
|
|
|
|
|
|
|
|
// 添加参数(自动处理编码)
|
|
|
|
|
urlParams.append('label', params.label);
|
|
|
|
|
urlParams.append('max_depth', params.max_depth);
|
|
|
|
|
urlParams.append('max_nodes', params.max_nodes);
|
|
|
|
|
params.note_ids.forEach(id => urlParams.append('note_ids', id));
|
|
|
|
|
|
|
|
|
|
// 添加API密钥(如果需要)
|
|
|
|
|
const apiKey = ''; // 替换为实际API密钥
|
|
|
|
|
if (apiKey) {
|
|
|
|
|
urlParams.append('api_key_header_value', apiKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fullUrl = `${baseUrl}?${urlParams.toString()}`;
|
|
|
|
|
console.log('知识图谱接口调用:', fullUrl);
|
|
|
|
|
|
|
|
|
|
// 3. 发起请求
|
|
|
|
|
fetch(fullUrl, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
mode: 'cors',
|
|
|
|
|
headers: { 'Accept': 'application/json' }
|
|
|
|
|
})
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
return response.json().then(err => {
|
|
|
|
|
throw new Error(err.detail?.[0]?.msg || `请求失败: ${response.status}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(kgData => {
|
|
|
|
|
// 验证返回数据格式
|
|
|
|
|
if (!kgData.nodes || !kgData.edges) {
|
|
|
|
|
throw new Error('知识图谱数据格式不正确');
|
|
|
|
|
}
|
|
|
|
|
// 2. 根据分类获取对应的文件列表
|
|
|
|
|
const fileCategories = JSON.parse(localStorage.getItem('fileCategories')) || {};
|
|
|
|
|
const categoryFiles = [];
|
|
|
|
|
|
|
|
|
|
Object.entries(fileCategories).forEach(([fileName, category]) => {
|
|
|
|
|
if (category === selectedCategory) {
|
|
|
|
|
categoryFiles.push(fileName);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 4. 处理节点连接数(用于节点大小显示)
|
|
|
|
|
const nodeLinkCount = {};
|
|
|
|
|
kgData.nodes.forEach(node => nodeLinkCount[node.id] = 0);
|
|
|
|
|
kgData.edges.forEach(edge => {
|
|
|
|
|
nodeLinkCount[edge.source]++;
|
|
|
|
|
nodeLinkCount[edge.target]++;
|
|
|
|
|
});
|
|
|
|
|
if (categoryFiles.length === 0) {
|
|
|
|
|
nodeInfo.className = 'error-message';
|
|
|
|
|
nodeInfo.innerHTML = `分类"${selectedCategory}"下没有找到文件`;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. 转换为ECharts所需格式
|
|
|
|
|
const echartsNodes = kgData.nodes.map(node => ({
|
|
|
|
|
id: node.id,
|
|
|
|
|
name: node.labels?.[0] || '未知节点',
|
|
|
|
|
category: node.properties?.entity_type || '默认类型',
|
|
|
|
|
properties: node.properties || {},
|
|
|
|
|
symbolSize: 20 + (nodeLinkCount[node.id] * 3) // 根据连接数调整节点大小
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const echartsLinks = kgData.edges.map(edge => ({
|
|
|
|
|
source: edge.source,
|
|
|
|
|
target: edge.target,
|
|
|
|
|
properties: edge.properties || {}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 提取分类(用于图例)
|
|
|
|
|
const categories = Array.from(new Set(echartsNodes.map(n => n.category)))
|
|
|
|
|
.map(type => ({ name: type }));
|
|
|
|
|
|
|
|
|
|
// 6. 初始化ECharts并渲染
|
|
|
|
|
const chart = echarts.init(document.getElementById('graph-container'));
|
|
|
|
|
|
|
|
|
|
// 设置图表配置
|
|
|
|
|
chart.setOption({
|
|
|
|
|
tooltip: {
|
|
|
|
|
formatter: function(params) {
|
|
|
|
|
return `<strong>${params.name}</strong><br>类型: ${params.data.category}`;
|
|
|
|
|
const params = {
|
|
|
|
|
label: document.getElementById('graphLabel').value.trim(),
|
|
|
|
|
max_depth: document.getElementById('graphMaxDepth').value.trim() || 3,
|
|
|
|
|
max_nodes: document.getElementById('graphMaxNodes').value.trim() || 100,
|
|
|
|
|
note_ids: categoryFiles // 使用分类对应的文件列表
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 表单验证
|
|
|
|
|
if (!params.label) {
|
|
|
|
|
nodeInfo.className = 'error-message';
|
|
|
|
|
nodeInfo.innerHTML = '请填写标签';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
legend: {
|
|
|
|
|
data: categories.map(c => c.name),
|
|
|
|
|
bottom: 10,
|
|
|
|
|
textStyle: { fontSize: 12 }
|
|
|
|
|
},
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'graph',
|
|
|
|
|
layout: 'force', // 力导向布局
|
|
|
|
|
roam: true, // 支持缩放和平移
|
|
|
|
|
draggable: true, // 节点可拖拽
|
|
|
|
|
force: {
|
|
|
|
|
repulsion: 300, // 节点之间的排斥力
|
|
|
|
|
edgeLength: 100, // 边的长度
|
|
|
|
|
gravity: 0.1, // gravity力,影响节点向中心聚集
|
|
|
|
|
iterations: 50 // 迭代次数,影响布局稳定性
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
overflow: 'truncate',
|
|
|
|
|
width: 60
|
|
|
|
|
},
|
|
|
|
|
categories: categories,
|
|
|
|
|
data: echartsNodes,
|
|
|
|
|
links: echartsLinks,
|
|
|
|
|
lineStyle: {
|
|
|
|
|
color: 'source', // 边的颜色和源节点一致
|
|
|
|
|
curveness: 0.1 // 边的弯曲度
|
|
|
|
|
},
|
|
|
|
|
emphasis: {
|
|
|
|
|
focus: 'adjacency', // 高亮相邻节点和边
|
|
|
|
|
lineStyle: {
|
|
|
|
|
width: 5 // 高亮时边的宽度
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 构建请求URL
|
|
|
|
|
const baseUrl = `${API_BASE_URL}/graphs/note`;
|
|
|
|
|
const urlParams = new URLSearchParams();
|
|
|
|
|
|
|
|
|
|
// 添加参数
|
|
|
|
|
urlParams.append('label', params.label);
|
|
|
|
|
urlParams.append('max_depth', params.max_depth);
|
|
|
|
|
urlParams.append('max_nodes', params.max_nodes);
|
|
|
|
|
params.note_ids.forEach(id => urlParams.append('note_ids', id));
|
|
|
|
|
|
|
|
|
|
// 添加API密钥
|
|
|
|
|
const apiKey = '';
|
|
|
|
|
if (apiKey) {
|
|
|
|
|
urlParams.append('api_key_header_value', apiKey);
|
|
|
|
|
}
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const fullUrl = `${baseUrl}?${urlParams.toString()}`;
|
|
|
|
|
console.log('知识图谱接口调用:', fullUrl);
|
|
|
|
|
|
|
|
|
|
// 4. 发起请求(后面的代码保持不变)
|
|
|
|
|
fetch(fullUrl, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
mode: 'cors',
|
|
|
|
|
headers: { 'Accept': 'application/json' }
|
|
|
|
|
})
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
return response.json().then(err => {
|
|
|
|
|
throw new Error(err.detail?.[0]?.msg || `请求失败: ${response.status}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(kgData => {
|
|
|
|
|
// 验证返回数据格式
|
|
|
|
|
if (!kgData.nodes || !kgData.edges) {
|
|
|
|
|
throw new Error('知识图谱数据格式不正确');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. 节点和边的点击事件
|
|
|
|
|
chart.on('click', function(params) {
|
|
|
|
|
if (params.dataType === 'node') {
|
|
|
|
|
const props = params.data.properties;
|
|
|
|
|
nodeInfo.innerHTML = `
|
|
|
|
|
<strong>${params.name}</strong> (${params.data.category})<br>
|
|
|
|
|
连接数: ${nodeLinkCount[params.data.id]}<br>
|
|
|
|
|
${props.description ? `描述: ${props.description.substring(0, 150)}${props.description.length > 150 ? '...' : ''}<br>` : ''}
|
|
|
|
|
${props.source ? `来源: ${props.source}` : ''}
|
|
|
|
|
`;
|
|
|
|
|
} else if (params.dataType === 'edge') {
|
|
|
|
|
const sourceNode = echartsNodes.find(n => n.id === params.data.source);
|
|
|
|
|
const targetNode = echartsNodes.find(n => n.id === params.data.target);
|
|
|
|
|
nodeInfo.innerHTML = `
|
|
|
|
|
<strong>关系</strong><br>
|
|
|
|
|
${sourceNode?.name || '未知节点'} → ${targetNode?.name || '未知节点'}<br>
|
|
|
|
|
${params.data.properties.description ? `描述: ${params.data.properties.description}` : ''}
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
// 处理节点连接数
|
|
|
|
|
const nodeLinkCount = {};
|
|
|
|
|
kgData.nodes.forEach(node => nodeLinkCount[node.id] = 0);
|
|
|
|
|
kgData.edges.forEach(edge => {
|
|
|
|
|
nodeLinkCount[edge.source]++;
|
|
|
|
|
nodeLinkCount[edge.target]++;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 转换为ECharts所需格式
|
|
|
|
|
const echartsNodes = kgData.nodes.map(node => ({
|
|
|
|
|
id: node.id,
|
|
|
|
|
name: node.labels?.[0] || '未知节点',
|
|
|
|
|
category: node.properties?.entity_type || '默认类型',
|
|
|
|
|
properties: node.properties || {},
|
|
|
|
|
symbolSize: 20 + (nodeLinkCount[node.id] * 3)
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const echartsLinks = kgData.edges.map(edge => ({
|
|
|
|
|
source: edge.source,
|
|
|
|
|
target: edge.target,
|
|
|
|
|
properties: edge.properties || {}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// 提取分类
|
|
|
|
|
const categories = Array.from(new Set(echartsNodes.map(n => n.category)))
|
|
|
|
|
.map(type => ({ name: type }));
|
|
|
|
|
|
|
|
|
|
// 初始化ECharts并渲染
|
|
|
|
|
const chart = echarts.init(document.getElementById('graph-container'));
|
|
|
|
|
|
|
|
|
|
chart.setOption({
|
|
|
|
|
tooltip: {
|
|
|
|
|
formatter: function(params) {
|
|
|
|
|
return `<strong>${params.name}</strong><br>类型: ${params.data.category}`;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
legend: {
|
|
|
|
|
data: categories.map(c => c.name),
|
|
|
|
|
bottom: 10,
|
|
|
|
|
textStyle: { fontSize: 12 }
|
|
|
|
|
},
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'graph',
|
|
|
|
|
layout: 'force',
|
|
|
|
|
roam: true,
|
|
|
|
|
draggable: true,
|
|
|
|
|
force: {
|
|
|
|
|
repulsion: 300,
|
|
|
|
|
edgeLength: 100,
|
|
|
|
|
gravity: 0.1,
|
|
|
|
|
iterations: 50
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
overflow: 'truncate',
|
|
|
|
|
width: 60
|
|
|
|
|
},
|
|
|
|
|
categories: categories,
|
|
|
|
|
data: echartsNodes,
|
|
|
|
|
links: echartsLinks,
|
|
|
|
|
lineStyle: {
|
|
|
|
|
color: 'source',
|
|
|
|
|
curveness: 0.1
|
|
|
|
|
},
|
|
|
|
|
emphasis: {
|
|
|
|
|
focus: 'adjacency',
|
|
|
|
|
lineStyle: {
|
|
|
|
|
width: 5
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 节点和边的点击事件
|
|
|
|
|
chart.on('click', function(params) {
|
|
|
|
|
if (params.dataType === 'node') {
|
|
|
|
|
const props = params.data.properties;
|
|
|
|
|
nodeInfo.innerHTML = `
|
|
|
|
|
<strong>${params.name}</strong> (${params.data.category})<br>
|
|
|
|
|
连接数: ${nodeLinkCount[params.data.id]}<br>
|
|
|
|
|
${props.description ? `描述: ${props.description.substring(0, 150)}${props.description.length > 150 ? '...' : ''}<br>` : ''}
|
|
|
|
|
${props.source ? `来源: ${props.source}` : ''}
|
|
|
|
|
`;
|
|
|
|
|
} else if (params.dataType === 'edge') {
|
|
|
|
|
const sourceNode = echartsNodes.find(n => n.id === params.data.source);
|
|
|
|
|
const targetNode = echartsNodes.find(n => n.id === params.data.target);
|
|
|
|
|
nodeInfo.innerHTML = `
|
|
|
|
|
<strong>关系</strong><br>
|
|
|
|
|
${sourceNode?.name || '未知节点'} → ${targetNode?.name || '未知节点'}<br>
|
|
|
|
|
${params.data.properties.description ? `描述: ${params.data.properties.description}` : ''}
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化时重绘
|
|
|
|
|
window.addEventListener('resize', () => chart.resize());
|
|
|
|
|
|
|
|
|
|
// 更新状态信息
|
|
|
|
|
nodeInfo.className = '';
|
|
|
|
|
nodeInfo.innerHTML = `
|
|
|
|
|
知识图谱加载完成 | 分类: ${selectedCategory} | 文件数: ${categoryFiles.length}<br>
|
|
|
|
|
节点数: ${echartsNodes.length} | 关系数: ${echartsLinks.length}<br>
|
|
|
|
|
提示: 可拖拽节点、缩放视图,点击节点/边查看详情
|
|
|
|
|
`;
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('知识图谱加载失败:', error);
|
|
|
|
|
nodeInfo.className = 'error-message';
|
|
|
|
|
nodeInfo.innerHTML = `加载失败: ${error.message}`;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化时重绘
|
|
|
|
|
window.addEventListener('resize', () => chart.resize());
|
|
|
|
|
|
|
|
|
|
// 更新状态信息
|
|
|
|
|
nodeInfo.className = '';
|
|
|
|
|
nodeInfo.innerHTML = `
|
|
|
|
|
知识图谱加载完成 | 节点数: ${echartsNodes.length} | 关系数: ${echartsLinks.length}<br>
|
|
|
|
|
提示: 可拖拽节点、缩放视图,点击节点/边查看详情
|
|
|
|
|
`;
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('知识图谱加载失败:', error);
|
|
|
|
|
nodeInfo.className = 'error-message';
|
|
|
|
|
nodeInfo.innerHTML = `加载失败: ${error.message}`;
|
|
|
|
|
});
|
|
|
|
|
// 初始化时渲染分类选择框
|
|
|
|
|
renderGraphCategoryFilter();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 修复知识图谱分类选择框渲染
|
|
|
|
|
function renderGraphCategoryFilter() {
|
|
|
|
|
const categorySelect = document.getElementById('graphCategory');
|
|
|
|
|
if (!categorySelect) {
|
|
|
|
|
console.error('未找到知识图谱分类选择框');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存当前选中的值
|
|
|
|
|
const currentValue = categorySelect.value;
|
|
|
|
|
|
|
|
|
|
// 清空现有选项
|
|
|
|
|
categorySelect.innerHTML = '<option value="">请选择分类</option>';
|
|
|
|
|
|
|
|
|
|
// 获取所有分类
|
|
|
|
|
const categories = getAllCategories();
|
|
|
|
|
|
|
|
|
|
// 生成选项
|
|
|
|
|
categories.forEach(category => {
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
option.value = category;
|
|
|
|
|
option.textContent = category;
|
|
|
|
|
categorySelect.appendChild(option);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 自动填充已上传的文档ID到知识图谱表单
|
|
|
|
|
function populateNoteIds() {
|
|
|
|
|
const noteIdsInput = document.getElementById('graphNoteIds');
|
|
|
|
|
if (noteIdsInput && Array.isArray(window.allDocuments)) {
|
|
|
|
|
// 提取所有已上传文档的文件名作为默认值
|
|
|
|
|
const docNames = window.allDocuments.map(doc => {
|
|
|
|
|
const pathParts = doc.file_path.split('/');
|
|
|
|
|
return pathParts[pathParts.length - 1]; // 获取文件名
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (docNames.length > 0) {
|
|
|
|
|
noteIdsInput.value = docNames.join(',');
|
|
|
|
|
}
|
|
|
|
|
// 恢复之前选中的值(如果还存在)
|
|
|
|
|
if (currentValue && categories.includes(currentValue)) {
|
|
|
|
|
categorySelect.value = currentValue;
|
|
|
|
|
} else if (categories.length > 0) {
|
|
|
|
|
// 否则选择第一个分类
|
|
|
|
|
categorySelect.value = categories[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 当文档列表加载完成后自动填充
|
|
|
|
|
// 在fetchAllDocuments函数的then回调中添加populateNoteIds()调用
|
|
|
|
|
// 修改原fetchAllDocuments函数的then部分:
|
|
|
|
|
const originalFetchAllDocumentsThen = fetchAllDocuments.toString().includes('renderDocumentsList')
|
|
|
|
|
? fetchAllDocuments.then
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
if (originalFetchAllDocumentsThen) {
|
|
|
|
|
// 这里是示意,实际需要在原fetchAllDocuments的then中添加:
|
|
|
|
|
// renderDocumentsList();
|
|
|
|
|
// populateNoteIds(); // 添加这一行
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
console.log(`知识图谱分类选择框已更新,共${categories.length}个分类`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|