From b3f7b8494b5471e025eb6de62f6f22f648e49627 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=85=AC=E6=98=8E?=
<83812544+Ed1s0nZ@users.noreply.github.com>
Date: Tue, 9 Jun 2026 21:06:14 +0800
Subject: [PATCH] Delete web/static/js/knowledge.js.bak
---
web/static/js/knowledge.js.bak | 2216 --------------------------------
1 file changed, 2216 deletions(-)
delete mode 100644 web/static/js/knowledge.js.bak
diff --git a/web/static/js/knowledge.js.bak b/web/static/js/knowledge.js.bak
deleted file mode 100644
index 00ece303..00000000
--- a/web/static/js/knowledge.js.bak
+++ /dev/null
@@ -1,2216 +0,0 @@
-// 知识库管理相关功能
-let knowledgeCategories = [];
-let knowledgeItems = [];
-let currentEditingItemId = null;
-let isSavingKnowledgeItem = false; // 防止重复提交
-let retrievalLogsData = []; // 存储检索日志数据,用于详情查看
-let knowledgePagination = {
- currentPage: 1,
- pageSize: 10, // 每页分类数(改为按分类分页)
- total: 0,
- currentCategory: ''
-};
-let knowledgeSearchTimeout = null; // 搜索防抖定时器
-
-// 加载知识分类
-async function loadKnowledgeCategories() {
- try {
- // 添加时间戳参数避免缓存
- const timestamp = Date.now();
- const response = await apiFetch(`/api/knowledge/categories?_t=${timestamp}`, {
- method: 'GET',
- headers: {
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
- 'Pragma': 'no-cache',
- 'Expires': '0'
- }
- });
- if (!response.ok) {
- throw new Error('获取分类失败');
- }
- const data = await response.json();
-
- // 检查知识库功能是否启用
- if (data.enabled === false) {
- // 功能未启用,显示友好提示
- const container = document.getElementById('knowledge-items-list');
- if (container) {
- container.innerHTML = `
-
-
📚
-
知识库功能未启用
-
${data.message || '请前往系统设置启用知识检索功能'}
-
-
- `;
- }
- return [];
- }
-
- knowledgeCategories = data.categories || [];
-
- // 更新分类筛选下拉框
- const filterDropdown = document.getElementById('knowledge-category-filter-dropdown');
- if (filterDropdown) {
- filterDropdown.innerHTML = '全部
';
- knowledgeCategories.forEach(category => {
- const option = document.createElement('div');
- option.className = 'custom-select-option';
- option.setAttribute('data-value', category);
- option.textContent = category;
- option.onclick = function() {
- selectKnowledgeCategory(category);
- };
- filterDropdown.appendChild(option);
- });
- }
-
- return knowledgeCategories;
- } catch (error) {
- console.error('加载分类失败:', error);
- // 只在非功能未启用的情况下显示错误
- if (!error.message.includes('知识库功能未启用')) {
- showNotification('加载分类失败: ' + error.message, 'error');
- }
- return [];
- }
-}
-
-// 加载知识项列表(支持按分类分页,默认不加载完整内容)
-async function loadKnowledgeItems(category = '', page = 1, pageSize = 10) {
- try {
- // 更新分页状态
- knowledgePagination.currentCategory = category;
- knowledgePagination.currentPage = page;
- knowledgePagination.pageSize = pageSize;
-
- // 构建URL(按分类分页模式,不包含完整内容)
- const timestamp = Date.now();
- const offset = (page - 1) * pageSize;
- let url = `/api/knowledge/items?categoryPage=true&limit=${pageSize}&offset=${offset}&_t=${timestamp}`;
- if (category) {
- url += `&category=${encodeURIComponent(category)}`;
- }
-
- const response = await apiFetch(url, {
- method: 'GET',
- headers: {
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
- 'Pragma': 'no-cache',
- 'Expires': '0'
- }
- });
-
- if (!response.ok) {
- throw new Error('获取知识项失败');
- }
- const data = await response.json();
-
- // 检查知识库功能是否启用
- if (data.enabled === false) {
- // 功能未启用,显示友好提示(如果还没有显示的话)
- const container = document.getElementById('knowledge-items-list');
- if (container && !container.querySelector('.empty-state')) {
- container.innerHTML = `
-
-
📚
-
知识库功能未启用
-
${data.message || '请前往系统设置启用知识检索功能'}
-
-
- `;
- }
- knowledgeItems = [];
- knowledgePagination.total = 0;
- renderKnowledgePagination();
- return [];
- }
-
- // 处理按分类分页的响应数据
- const categoriesWithItems = data.categories || [];
- knowledgePagination.total = data.total || 0; // 总分类数
-
- renderKnowledgeItemsByCategories(categoriesWithItems);
-
- // 如果选择了单个分类,不显示分页(因为只显示一个分类)
- if (category) {
- const paginationContainer = document.getElementById('knowledge-pagination');
- if (paginationContainer) {
- paginationContainer.innerHTML = '';
- }
- } else {
- renderKnowledgePagination();
- }
- return categoriesWithItems;
- } catch (error) {
- console.error('加载知识项失败:', error);
- // 只在非功能未启用的情况下显示错误
- if (!error.message.includes('知识库功能未启用')) {
- showNotification('加载知识项失败: ' + error.message, 'error');
- }
- return [];
- }
-}
-
-// 渲染知识项列表(按分类分页的数据结构)
-function renderKnowledgeItemsByCategories(categoriesWithItems) {
- const container = document.getElementById('knowledge-items-list');
- if (!container) return;
-
- if (categoriesWithItems.length === 0) {
- container.innerHTML = '暂无知识项
';
- return;
- }
-
- // 计算总项数和分类数
- const totalItems = categoriesWithItems.reduce((sum, cat) => sum + (cat.items?.length || 0), 0);
- const categoryCount = categoriesWithItems.length;
-
- // 更新统计信息
- updateKnowledgeStats(categoriesWithItems, categoryCount);
-
- // 渲染分类及知识项
- let html = '';
-
- categoriesWithItems.forEach(categoryData => {
- const category = categoryData.category || '未分类';
- const categoryItems = categoryData.items || [];
- const categoryCount = categoryData.itemCount || categoryItems.length;
-
- html += `
-
-
-
- ${categoryItems.map(item => renderKnowledgeItemCard(item)).join('')}
-
-
- `;
- });
-
- html += '
';
- container.innerHTML = html;
-}
-
-// 渲染知识项列表(向后兼容,用于按项分页的旧代码)
-function renderKnowledgeItems(items) {
- const container = document.getElementById('knowledge-items-list');
- if (!container) return;
-
- if (items.length === 0) {
- container.innerHTML = '暂无知识项
';
- return;
- }
-
- // 按分类分组
- const groupedByCategory = {};
- items.forEach(item => {
- const category = item.category || '未分类';
- if (!groupedByCategory[category]) {
- groupedByCategory[category] = [];
- }
- groupedByCategory[category].push(item);
- });
-
- // 更新统计信息
- updateKnowledgeStats(items, Object.keys(groupedByCategory).length);
-
- // 渲染分组后的内容
- const categories = Object.keys(groupedByCategory).sort();
- let html = '';
-
- categories.forEach(category => {
- const categoryItems = groupedByCategory[category];
- const categoryCount = categoryItems.length;
-
- html += `
-
-
-
- ${categoryItems.map(item => renderKnowledgeItemCard(item)).join('')}
-
-
- `;
- });
-
- html += '
';
- container.innerHTML = html;
-}
-
-// 渲染分页控件(按分类分页)
-function renderKnowledgePagination() {
- const container = document.getElementById('knowledge-pagination');
- if (!container) return;
-
- const { currentPage, pageSize, total } = knowledgePagination;
- const totalPages = Math.ceil(total / pageSize); // total是总分类数
-
- if (totalPages <= 1) {
- container.innerHTML = '';
- return;
- }
-
- let html = '';
- container.innerHTML = html;
-}
-
-// 加载指定页码的知识项
-function loadKnowledgePage(page) {
- const { currentCategory, pageSize, total } = knowledgePagination;
- const totalPages = Math.ceil(total / pageSize);
-
- if (page < 1 || page > totalPages) {
- return;
- }
-
- loadKnowledgeItems(currentCategory, page, pageSize);
-}
-
-// 渲染单个知识项卡片
-function renderKnowledgeItemCard(item) {
- // 提取内容预览(如果item没有content字段,说明是摘要,不显示预览)
- let previewText = '';
- if (item.content) {
- // 去除markdown格式,取前150字符
- let preview = item.content;
- // 移除markdown标题标记
- preview = preview.replace(/^#+\s+/gm, '');
- // 移除代码块
- preview = preview.replace(/```[\s\S]*?```/g, '');
- // 移除行内代码
- preview = preview.replace(/`[^`]+`/g, '');
- // 移除链接
- preview = preview.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1');
- // 清理多余空白
- preview = preview.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
-
- previewText = preview.length > 150 ? preview.substring(0, 150) + '...' : preview;
- }
-
- // 提取文件路径显示
- const filePath = item.filePath || '';
- const relativePath = filePath.split(/[/\\]/).slice(-2).join('/'); // 显示最后两级路径
-
- // 格式化时间
- const createdTime = formatTime(item.createdAt);
- const updatedTime = formatTime(item.updatedAt);
-
- // 优先显示更新时间,如果没有更新时间则显示创建时间
- const displayTime = updatedTime || createdTime;
- const timeLabel = updatedTime ? '更新时间' : '创建时间';
-
- // 判断是否为最近更新(7天内)
- let isRecent = false;
- if (item.updatedAt && updatedTime) {
- const updateDate = new Date(item.updatedAt);
- if (!isNaN(updateDate.getTime())) {
- isRecent = (Date.now() - updateDate.getTime()) < 7 * 24 * 60 * 60 * 1000;
- }
- }
-
- return `
-
-
- ${previewText ? `
-
-
${escapeHtml(previewText)}
-
- ` : ''}
-
-
- `;
-}
-
-// 更新统计信息(支持按分类分页的数据结构)
-function updateKnowledgeStats(data, categoryCount) {
- const statsContainer = document.getElementById('knowledge-stats');
- if (!statsContainer) return;
-
- // 计算当前页的知识项数
- let currentPageItemCount = 0;
- if (Array.isArray(data) && data.length > 0) {
- // 判断是categoriesWithItems还是items数组
- if (data[0].category !== undefined && data[0].items !== undefined) {
- // 是按分类分页的数据结构
- currentPageItemCount = data.reduce((sum, cat) => sum + (cat.items?.length || 0), 0);
- } else {
- // 是按项分页的数据结构(向后兼容)
- currentPageItemCount = data.length;
- }
- }
-
- // 总分类数(来自分页信息,只有在未定义时才使用当前页分类数作为后备值)
- const totalCategories = (knowledgePagination.total != null) ? knowledgePagination.total : categoryCount;
-
- statsContainer.innerHTML = `
-
- 总分类数
- ${totalCategories}
-
-
- 当前页分类
- ${categoryCount} 个
-
-
- 当前页知识项
- ${currentPageItemCount} 项
-
- `;
-
- // 更新索引进度
- updateIndexProgress();
-}
-
-// 更新索引进度
-let indexProgressInterval = null;
-
-async function updateIndexProgress() {
- try {
- const response = await apiFetch('/api/knowledge/index-status', {
- method: 'GET',
- headers: {
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
- 'Pragma': 'no-cache',
- 'Expires': '0'
- }
- });
-
- if (!response.ok) {
- return; // 静默失败,不影响主界面
- }
-
- const status = await response.json();
- const progressContainer = document.getElementById('knowledge-index-progress');
- if (!progressContainer) return;
-
- // 检查知识库功能是否启用
- if (status.enabled === false) {
- // 功能未启用,隐藏进度条
- progressContainer.style.display = 'none';
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- return;
- }
-
- const totalItems = status.total_items || 0;
- const indexedItems = status.indexed_items || 0;
- const progressPercent = status.progress_percent || 0;
- const isComplete = status.is_complete || false;
- const lastError = status.last_error || '';
-
- if (totalItems === 0) {
- // 没有知识项,隐藏进度条
- progressContainer.style.display = 'none';
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- return;
- }
-
- // 显示进度条
- progressContainer.style.display = 'block';
-
- // 如果有错误信息,显示错误
- if (lastError) {
- progressContainer.innerHTML = `
-
-
- ❌
- 索引构建失败
-
-
- ${escapeHtml(lastError)}
-
-
- 可能的原因:嵌入模型配置错误、API密钥无效、余额不足等。请检查配置后重试。
-
-
-
-
-
-
- `;
- // 停止轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- // 显示错误通知
- showNotification('索引构建失败: ' + lastError.substring(0, 100), 'error');
- return;
- }
-
- if (isComplete) {
- progressContainer.innerHTML = `
-
- ✅
- 索引构建完成 (${indexedItems}/${totalItems})
-
- `;
- // 完成后停止轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- } else {
- progressContainer.innerHTML = `
-
-
-
-
索引构建完成后,语义搜索功能将可用
-
- `;
-
- // 如果还没有开始轮询,开始轮询
- if (!indexProgressInterval) {
- indexProgressInterval = setInterval(updateIndexProgress, 3000); // 每3秒刷新一次
- }
- }
- } catch (error) {
- // 显示错误信息
- console.error('获取索引状态失败:', error);
- const progressContainer = document.getElementById('knowledge-index-progress');
- if (progressContainer) {
- progressContainer.style.display = 'block';
- progressContainer.innerHTML = `
-
-
- ⚠️
- 无法获取索引状态
-
-
- 无法连接到服务器获取索引状态,请检查网络连接或刷新页面。
-
-
- `;
- }
- // 停止轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- }
-}
-
-// 停止索引进度轮询
-function stopIndexProgressPolling() {
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- const progressContainer = document.getElementById('knowledge-index-progress');
- if (progressContainer) {
- progressContainer.style.display = 'none';
- }
-}
-
-// 选择知识分类
-function selectKnowledgeCategory(category) {
- const trigger = document.getElementById('knowledge-category-filter-trigger');
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- const dropdown = document.getElementById('knowledge-category-filter-dropdown');
-
- if (trigger && wrapper && dropdown) {
- const displayText = category || '全部';
- trigger.querySelector('span').textContent = displayText;
- wrapper.classList.remove('open');
-
- // 更新选中状态
- dropdown.querySelectorAll('.custom-select-option').forEach(opt => {
- opt.classList.remove('selected');
- if (opt.getAttribute('data-value') === category) {
- opt.classList.add('selected');
- }
- });
- }
- // 切换分类时重置到第一页(如果选择了分类,API会返回该分类的所有项)
- loadKnowledgeItems(category, 1, knowledgePagination.pageSize);
-}
-
-// 筛选知识项
-function filterKnowledgeItems() {
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- if (wrapper) {
- const selectedOption = wrapper.querySelector('.custom-select-option.selected');
- const category = selectedOption ? selectedOption.getAttribute('data-value') : '';
- // 重置到第一页
- loadKnowledgeItems(category, 1, knowledgePagination.pageSize);
- }
-}
-
-// 处理搜索输入(带防抖)
-function handleKnowledgeSearchInput() {
- const searchInput = document.getElementById('knowledge-search');
- const searchTerm = searchInput?.value.trim() || '';
-
- // 清除之前的定时器
- if (knowledgeSearchTimeout) {
- clearTimeout(knowledgeSearchTimeout);
- }
-
- // 如果搜索框为空,立即恢复列表
- if (!searchTerm) {
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- let category = '';
- if (wrapper) {
- const selectedOption = wrapper.querySelector('.custom-select-option.selected');
- category = selectedOption ? selectedOption.getAttribute('data-value') : '';
- }
- loadKnowledgeItems(category, 1, knowledgePagination.pageSize);
- return;
- }
-
- // 有搜索词时,延迟500ms后执行搜索(防抖)
- knowledgeSearchTimeout = setTimeout(() => {
- searchKnowledgeItems();
- }, 500);
-}
-
-// 搜索知识项(后端关键字匹配,在所有数据中搜索)
-async function searchKnowledgeItems() {
- const searchInput = document.getElementById('knowledge-search');
- const searchTerm = searchInput?.value.trim() || '';
-
- if (!searchTerm) {
- // 恢复原始列表(重置到第一页)
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- let category = '';
- if (wrapper) {
- const selectedOption = wrapper.querySelector('.custom-select-option.selected');
- category = selectedOption ? selectedOption.getAttribute('data-value') : '';
- }
- await loadKnowledgeItems(category, 1, knowledgePagination.pageSize);
- return;
- }
-
- try {
- // 获取当前选择的分类
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- let category = '';
- if (wrapper) {
- const selectedOption = wrapper.querySelector('.custom-select-option.selected');
- category = selectedOption ? selectedOption.getAttribute('data-value') : '';
- }
-
- // 调用后端API进行全量搜索
- const timestamp = Date.now();
- let url = `/api/knowledge/items?search=${encodeURIComponent(searchTerm)}&_t=${timestamp}`;
- if (category) {
- url += `&category=${encodeURIComponent(category)}`;
- }
-
- const response = await apiFetch(url, {
- method: 'GET',
- headers: {
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
- 'Pragma': 'no-cache',
- 'Expires': '0'
- }
- });
-
- if (!response.ok) {
- throw new Error('搜索失败');
- }
-
- const data = await response.json();
-
- // 检查知识库功能是否启用
- if (data.enabled === false) {
- const container = document.getElementById('knowledge-items-list');
- if (container) {
- container.innerHTML = `
-
-
📚
-
知识库功能未启用
-
${data.message || '请前往系统设置启用知识检索功能'}
-
-
- `;
- }
- return;
- }
-
- // 处理搜索结果
- const categoriesWithItems = data.categories || [];
-
- // 渲染搜索结果
- const container = document.getElementById('knowledge-items-list');
- if (!container) return;
-
- if (categoriesWithItems.length === 0) {
- container.innerHTML = `
-
-
🔍
-
未找到匹配的知识项
-
关键词 "${escapeHtml(searchTerm)}" 在所有数据中没有匹配结果
-
请尝试其他关键词,或使用分类筛选功能
-
- `;
- } else {
- // 计算总项数和分类数
- const totalItems = categoriesWithItems.reduce((sum, cat) => sum + (cat.items?.length || 0), 0);
- const categoryCount = categoriesWithItems.length;
-
- // 更新统计信息
- updateKnowledgeStats(categoriesWithItems, categoryCount);
-
- // 渲染搜索结果
- renderKnowledgeItemsByCategories(categoriesWithItems);
- }
-
- // 搜索时隐藏分页(因为搜索结果显示所有匹配结果)
- const paginationContainer = document.getElementById('knowledge-pagination');
- if (paginationContainer) {
- paginationContainer.innerHTML = '';
- }
-
- } catch (error) {
- console.error('搜索知识项失败:', error);
- showNotification('搜索失败: ' + error.message, 'error');
- }
-}
-
-// 刷新知识库
-async function refreshKnowledgeBase() {
- try {
- showNotification('正在扫描知识库...', 'info');
- const response = await apiFetch('/api/knowledge/scan', {
- method: 'POST'
- });
- if (!response.ok) {
- throw new Error('扫描知识库失败');
- }
- const data = await response.json();
- // 根据返回的消息显示不同的提示
- if (data.items_to_index && data.items_to_index > 0) {
- showNotification(`扫描完成,开始索引 ${data.items_to_index} 个新添加或更新的知识项`, 'success');
- } else {
- showNotification(data.message || '扫描完成,没有需要索引的新项或更新项', 'success');
- }
- // 重新加载知识项(重置到第一页)
- await loadKnowledgeCategories();
- await loadKnowledgeItems(knowledgePagination.currentCategory, 1, knowledgePagination.pageSize);
-
- // 停止现有的轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
-
- // 如果有需要索引的项,等待一小段时间后立即更新进度
- if (data.items_to_index && data.items_to_index > 0) {
- await new Promise(resolve => setTimeout(resolve, 500));
- updateIndexProgress();
- // 开始轮询进度(每2秒刷新一次)
- if (!indexProgressInterval) {
- indexProgressInterval = setInterval(updateIndexProgress, 2000);
- }
- } else {
- // 没有需要索引的项,也更新一次以显示当前状态
- updateIndexProgress();
- }
- } catch (error) {
- console.error('刷新知识库失败:', error);
- showNotification('刷新知识库失败: ' + error.message, 'error');
- }
-}
-
-// 重建索引
-async function rebuildKnowledgeIndex() {
- try {
- if (!confirm('确定要重建索引吗?这可能需要一些时间。')) {
- return;
- }
- showNotification('正在重建索引...', 'info');
-
- // 先停止现有的轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
-
- // 立即显示"正在重建"状态,因为重建开始时会清空旧索引
- const progressContainer = document.getElementById('knowledge-index-progress');
- if (progressContainer) {
- progressContainer.style.display = 'block';
- progressContainer.innerHTML = `
-
-
-
-
索引构建完成后,语义搜索功能将可用
-
- `;
- }
-
- const response = await apiFetch('/api/knowledge/index', {
- method: 'POST'
- });
- if (!response.ok) {
- throw new Error('重建索引失败');
- }
- showNotification('索引重建已开始,将在后台进行', 'success');
-
- // 等待一小段时间,确保后端已经开始处理并清空了旧索引
- await new Promise(resolve => setTimeout(resolve, 500));
-
- // 立即更新一次进度
- updateIndexProgress();
-
- // 开始轮询进度(每2秒刷新一次,比默认的3秒更频繁)
- if (!indexProgressInterval) {
- indexProgressInterval = setInterval(updateIndexProgress, 2000);
- }
- } catch (error) {
- console.error('重建索引失败:', error);
- showNotification('重建索引失败: ' + error.message, 'error');
- }
-}
-
-// 显示添加知识项模态框
-function showAddKnowledgeItemModal() {
- currentEditingItemId = null;
- document.getElementById('knowledge-item-modal-title').textContent = '添加知识';
- document.getElementById('knowledge-item-category').value = '';
- document.getElementById('knowledge-item-title').value = '';
- document.getElementById('knowledge-item-content').value = '';
- document.getElementById('knowledge-item-modal').style.display = 'block';
-}
-
-// 编辑知识项
-async function editKnowledgeItem(id) {
- try {
- const response = await apiFetch(`/api/knowledge/items/${id}`);
- if (!response.ok) {
- throw new Error('获取知识项失败');
- }
- const item = await response.json();
-
- currentEditingItemId = id;
- document.getElementById('knowledge-item-modal-title').textContent = '编辑知识';
- document.getElementById('knowledge-item-category').value = item.category;
- document.getElementById('knowledge-item-title').value = item.title;
- document.getElementById('knowledge-item-content').value = item.content;
- document.getElementById('knowledge-item-modal').style.display = 'block';
- } catch (error) {
- console.error('编辑知识项失败:', error);
- showNotification('编辑知识项失败: ' + error.message, 'error');
- }
-}
-
-// 保存知识项
-async function saveKnowledgeItem() {
- // 防止重复提交
- if (isSavingKnowledgeItem) {
- showNotification('正在保存中,请勿重复点击...', 'warning');
- return;
- }
-
- const category = document.getElementById('knowledge-item-category').value.trim();
- const title = document.getElementById('knowledge-item-title').value.trim();
- const content = document.getElementById('knowledge-item-content').value.trim();
-
- if (!category || !title || !content) {
- showNotification('请填写所有必填字段', 'error');
- return;
- }
-
- // 设置保存中标志
- isSavingKnowledgeItem = true;
-
- // 获取保存按钮和取消按钮
- const saveButton = document.querySelector('#knowledge-item-modal .modal-footer .btn-primary');
- const cancelButton = document.querySelector('#knowledge-item-modal .modal-footer .btn-secondary');
- const modal = document.getElementById('knowledge-item-modal');
-
- const originalButtonText = saveButton ? saveButton.textContent : '保存';
- const originalButtonDisabled = saveButton ? saveButton.disabled : false;
-
- // 禁用所有输入字段和按钮
- const categoryInput = document.getElementById('knowledge-item-category');
- const titleInput = document.getElementById('knowledge-item-title');
- const contentInput = document.getElementById('knowledge-item-content');
-
- if (categoryInput) categoryInput.disabled = true;
- if (titleInput) titleInput.disabled = true;
- if (contentInput) contentInput.disabled = true;
- if (cancelButton) cancelButton.disabled = true;
-
- // 设置保存按钮加载状态
- if (saveButton) {
- saveButton.disabled = true;
- saveButton.style.opacity = '0.6';
- saveButton.style.cursor = 'not-allowed';
- saveButton.textContent = '保存中...';
- }
-
- try {
- const url = currentEditingItemId
- ? `/api/knowledge/items/${currentEditingItemId}`
- : '/api/knowledge/items';
- const method = currentEditingItemId ? 'PUT' : 'POST';
-
- const response = await apiFetch(url, {
- method: method,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- category,
- title,
- content
- })
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.error || '保存知识项失败');
- }
-
- const item = await response.json();
- const action = currentEditingItemId ? '更新' : '创建';
- const newItemCategory = item.category || category; // 保存新添加的知识项分类
-
- // 获取当前筛选状态,以便刷新后保持
- const currentCategory = document.getElementById('knowledge-category-filter-wrapper');
- let selectedCategory = '';
- if (currentCategory) {
- const selectedOption = currentCategory.querySelector('.custom-select-option.selected');
- if (selectedOption) {
- selectedCategory = selectedOption.getAttribute('data-value') || '';
- }
- }
-
- // 立即关闭模态框,给用户明确的反馈
- closeKnowledgeItemModal();
-
- // 显示加载状态并刷新数据(等待完成以确保数据同步)
- const itemsListContainer = document.getElementById('knowledge-items-list');
- const originalContent = itemsListContainer ? itemsListContainer.innerHTML : '';
-
- if (itemsListContainer) {
- itemsListContainer.innerHTML = '刷新中...
';
- }
-
- try {
- // 先刷新分类,再刷新知识项
- console.log('开始刷新知识库数据...');
- await loadKnowledgeCategories();
- console.log('分类刷新完成,开始刷新知识项...');
-
- // 如果新添加的知识项不在当前筛选的分类中,切换到该分类显示
- let categoryToShow = selectedCategory;
- if (!currentEditingItemId && selectedCategory && selectedCategory !== '' && newItemCategory !== selectedCategory) {
- // 新添加的知识项,如果当前筛选的不是该分类,切换到新知识项的分类
- categoryToShow = newItemCategory;
- // 更新筛选器显示(不触发加载,因为我们下面会手动加载)
- const trigger = document.getElementById('knowledge-category-filter-trigger');
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- const dropdown = document.getElementById('knowledge-category-filter-dropdown');
- if (trigger && wrapper && dropdown) {
- trigger.querySelector('span').textContent = newItemCategory || '全部';
- dropdown.querySelectorAll('.custom-select-option').forEach(opt => {
- opt.classList.remove('selected');
- if (opt.getAttribute('data-value') === newItemCategory) {
- opt.classList.add('selected');
- }
- });
- }
- showNotification(`✅ ${action}成功!已切换到分类"${newItemCategory}"查看新添加的知识项。`, 'success');
- }
-
- // 刷新知识项列表(重置到第一页)
- await loadKnowledgeItems(categoryToShow, 1, knowledgePagination.pageSize);
- console.log('知识项刷新完成');
- } catch (err) {
- console.error('刷新数据失败:', err);
- // 如果刷新失败,恢复原内容
- if (itemsListContainer && originalContent) {
- itemsListContainer.innerHTML = originalContent;
- }
- showNotification('⚠️ 知识项已保存,但刷新列表失败,请手动刷新页面查看', 'warning');
- }
-
- } catch (error) {
- console.error('保存知识项失败:', error);
- showNotification('❌ 保存知识项失败: ' + error.message, 'error');
-
- // 如果通知系统不可用,使用alert
- if (typeof window.showNotification !== 'function') {
- alert('❌ 保存知识项失败: ' + error.message);
- }
-
- // 恢复输入字段和按钮状态(错误时不关闭模态框,让用户修改后重试)
- if (categoryInput) categoryInput.disabled = false;
- if (titleInput) titleInput.disabled = false;
- if (contentInput) contentInput.disabled = false;
- if (cancelButton) cancelButton.disabled = false;
- if (saveButton) {
- saveButton.disabled = false;
- saveButton.style.opacity = '';
- saveButton.style.cursor = '';
- saveButton.textContent = originalButtonText;
- }
- } finally {
- // 清除保存中标志
- isSavingKnowledgeItem = false;
- }
-}
-
-// 删除知识项
-async function deleteKnowledgeItem(id) {
- if (!confirm('确定要删除这个知识项吗?')) {
- return;
- }
-
- // 找到要删除的知识项卡片和删除按钮
- const itemCard = document.querySelector(`.knowledge-item-card[data-id="${id}"]`);
- const deleteButton = itemCard ? itemCard.querySelector('.knowledge-item-delete-btn') : null;
- const categorySection = itemCard ? itemCard.closest('.knowledge-category-section') : null;
- let originalDisplay = '';
- let originalOpacity = '';
- let originalButtonOpacity = '';
-
- // 设置删除按钮的加载状态
- if (deleteButton) {
- originalButtonOpacity = deleteButton.style.opacity;
- deleteButton.style.opacity = '0.5';
- deleteButton.style.cursor = 'not-allowed';
- deleteButton.disabled = true;
-
- // 添加加载动画
- const svg = deleteButton.querySelector('svg');
- if (svg) {
- svg.style.animation = 'spin 1s linear infinite';
- }
- }
-
- // 立即从UI中移除该项(乐观更新)
- if (itemCard) {
- originalDisplay = itemCard.style.display;
- originalOpacity = itemCard.style.opacity;
- itemCard.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
- itemCard.style.opacity = '0';
- itemCard.style.transform = 'translateX(-20px)';
-
- // 等待动画完成后移除
- setTimeout(() => {
- if (itemCard.parentElement) {
- itemCard.remove();
-
- // 检查分类是否还有项目,如果没有则隐藏分类标题
- if (categorySection) {
- const remainingItems = categorySection.querySelectorAll('.knowledge-item-card');
- if (remainingItems.length === 0) {
- categorySection.style.transition = 'opacity 0.3s ease-out';
- categorySection.style.opacity = '0';
- setTimeout(() => {
- if (categorySection.parentElement) {
- categorySection.remove();
- }
- }, 300);
- } else {
- // 更新分类计数
- const categoryCount = categorySection.querySelector('.knowledge-category-count');
- if (categoryCount) {
- const newCount = remainingItems.length;
- categoryCount.textContent = `${newCount} 项`;
- }
- }
- }
-
- // 不在这里更新统计信息,等待重新加载数据后由正确的逻辑更新
- }
- }, 300);
- }
-
- try {
- const response = await apiFetch(`/api/knowledge/items/${id}`, {
- method: 'DELETE'
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.error || '删除知识项失败');
- }
-
- // 显示成功通知
- showNotification('✅ 删除成功!知识项已从系统中移除。', 'success');
-
- // 重新加载数据以确保数据同步(保持当前页码)
- await loadKnowledgeCategories();
- await loadKnowledgeItems(knowledgePagination.currentCategory, knowledgePagination.currentPage, knowledgePagination.pageSize);
-
- } catch (error) {
- console.error('删除知识项失败:', error);
-
- // 如果删除失败,恢复该项显示
- if (itemCard && originalDisplay !== 'none') {
- itemCard.style.display = originalDisplay || '';
- itemCard.style.opacity = originalOpacity || '1';
- itemCard.style.transform = '';
- itemCard.style.transition = '';
-
- // 如果分类被移除了,需要恢复
- if (categorySection && !categorySection.parentElement) {
- // 需要重新加载来恢复(保持当前分页状态)
- await loadKnowledgeItems(knowledgePagination.currentCategory, knowledgePagination.currentPage, knowledgePagination.pageSize);
- }
- }
-
- // 恢复删除按钮状态
- if (deleteButton) {
- deleteButton.style.opacity = originalButtonOpacity || '';
- deleteButton.style.cursor = '';
- deleteButton.disabled = false;
- const svg = deleteButton.querySelector('svg');
- if (svg) {
- svg.style.animation = '';
- }
- }
-
- showNotification('❌ 删除知识项失败: ' + error.message, 'error');
- }
-}
-
-// 临时更新统计信息(删除后)
-function updateKnowledgeStatsAfterDelete() {
- const statsContainer = document.getElementById('knowledge-stats');
- if (!statsContainer) return;
-
- const allItems = document.querySelectorAll('.knowledge-item-card');
- const allCategories = document.querySelectorAll('.knowledge-category-section');
-
- const totalItems = allItems.length;
- const categoryCount = allCategories.length;
-
- // 计算总内容大小(这里简化处理,实际应该从服务器获取)
- const statsItems = statsContainer.querySelectorAll('.knowledge-stat-item');
- if (statsItems.length >= 2) {
- const totalItemsSpan = statsItems[0].querySelector('.knowledge-stat-value');
- const categoryCountSpan = statsItems[1].querySelector('.knowledge-stat-value');
-
- if (totalItemsSpan) {
- totalItemsSpan.textContent = totalItems;
- }
- if (categoryCountSpan) {
- categoryCountSpan.textContent = categoryCount;
- }
- }
-}
-
-// 关闭知识项模态框
-function closeKnowledgeItemModal() {
- const modal = document.getElementById('knowledge-item-modal');
- if (modal) {
- modal.style.display = 'none';
- }
-
- // 重置编辑状态
- currentEditingItemId = null;
- isSavingKnowledgeItem = false;
-
- // 恢复所有输入字段和按钮状态
- const categoryInput = document.getElementById('knowledge-item-category');
- const titleInput = document.getElementById('knowledge-item-title');
- const contentInput = document.getElementById('knowledge-item-content');
- const saveButton = document.querySelector('#knowledge-item-modal .modal-footer .btn-primary');
- const cancelButton = document.querySelector('#knowledge-item-modal .modal-footer .btn-secondary');
-
- if (categoryInput) {
- categoryInput.disabled = false;
- categoryInput.value = '';
- }
- if (titleInput) {
- titleInput.disabled = false;
- titleInput.value = '';
- }
- if (contentInput) {
- contentInput.disabled = false;
- contentInput.value = '';
- }
- if (saveButton) {
- saveButton.disabled = false;
- saveButton.style.opacity = '';
- saveButton.style.cursor = '';
- saveButton.textContent = '保存';
- }
- if (cancelButton) {
- cancelButton.disabled = false;
- }
-}
-
-// 加载检索日志
-async function loadRetrievalLogs(conversationId = '', messageId = '') {
- try {
- let url = '/api/knowledge/retrieval-logs?limit=100';
- if (conversationId) {
- url += `&conversationId=${encodeURIComponent(conversationId)}`;
- }
- if (messageId) {
- url += `&messageId=${encodeURIComponent(messageId)}`;
- }
-
- const response = await apiFetch(url);
- if (!response.ok) {
- throw new Error('获取检索日志失败');
- }
- const data = await response.json();
- renderRetrievalLogs(data.logs || []);
- } catch (error) {
- console.error('加载检索日志失败:', error);
- // 即使加载失败,也显示空状态而不是一直显示"加载中..."
- renderRetrievalLogs([]);
- // 只在非空筛选条件下才显示错误通知(避免在没有数据时显示错误)
- if (conversationId || messageId) {
- showNotification('加载检索日志失败: ' + error.message, 'error');
- }
- }
-}
-
-// 渲染检索日志
-function renderRetrievalLogs(logs) {
- const container = document.getElementById('retrieval-logs-list');
- if (!container) return;
-
- // 更新统计信息(即使为空数组也要更新)
- updateRetrievalStats(logs);
-
- if (logs.length === 0) {
- container.innerHTML = '暂无检索记录
';
- retrievalLogsData = [];
- return;
- }
-
- // 保存日志数据供详情查看使用
- retrievalLogsData = logs;
-
- container.innerHTML = logs.map((log, index) => {
- // 处理retrievedItems:可能是数组、字符串数组,或者特殊标记
- let itemCount = 0;
- let hasResults = false;
-
- if (log.retrievedItems) {
- if (Array.isArray(log.retrievedItems)) {
- // 过滤掉特殊标记
- const realItems = log.retrievedItems.filter(id => id !== '_has_results');
- itemCount = realItems.length;
- // 如果有特殊标记,表示有结果但ID未知,显示为"有结果"
- if (log.retrievedItems.includes('_has_results')) {
- hasResults = true;
- // 如果有真实ID,使用真实数量;否则显示为"有结果"(不显示具体数量)
- if (itemCount === 0) {
- itemCount = -1; // -1 表示有结果但数量未知
- }
- } else {
- hasResults = itemCount > 0;
- }
- } else if (typeof log.retrievedItems === 'string') {
- // 如果是字符串,尝试解析JSON
- try {
- const parsed = JSON.parse(log.retrievedItems);
- if (Array.isArray(parsed)) {
- const realItems = parsed.filter(id => id !== '_has_results');
- itemCount = realItems.length;
- if (parsed.includes('_has_results')) {
- hasResults = true;
- if (itemCount === 0) {
- itemCount = -1;
- }
- } else {
- hasResults = itemCount > 0;
- }
- }
- } catch (e) {
- // 解析失败,忽略
- }
- }
- }
-
- const timeAgo = getTimeAgo(log.createdAt);
-
- return `
-
-
-
-
- ${log.conversationId ? `
-
- 对话ID
- ${escapeHtml(log.conversationId)}
-
- ` : ''}
- ${log.messageId ? `
-
- 消息ID
- ${escapeHtml(log.messageId)}
-
- ` : ''}
-
- 检索结果
-
- ${hasResults ? (itemCount > 0 ? `找到 ${itemCount} 个相关知识项` : '找到相关知识项(数量未知)') : '未找到匹配的知识项'}
-
-
-
- ${hasResults && log.retrievedItems && log.retrievedItems.length > 0 ? `
-
-
检索到的知识项:
-
- ${log.retrievedItems.slice(0, 3).map((itemId, idx) => `
- ${idx + 1}
- `).join('')}
- ${log.retrievedItems.length > 3 ? `+${log.retrievedItems.length - 3}` : ''}
-
-
- ` : ''}
-
-
-
-
-
-
- `;
- }).join('');
-}
-
-// 更新检索统计信息
-function updateRetrievalStats(logs) {
- const statsContainer = document.getElementById('retrieval-stats');
- if (!statsContainer) return;
-
- const totalLogs = logs.length;
- // 判断是否有结果:检查retrievedItems数组,过滤掉特殊标记后长度>0,或者包含特殊标记
- const successfulLogs = logs.filter(log => {
- if (!log.retrievedItems) return false;
- if (Array.isArray(log.retrievedItems)) {
- const realItems = log.retrievedItems.filter(id => id !== '_has_results');
- return realItems.length > 0 || log.retrievedItems.includes('_has_results');
- }
- return false;
- }).length;
- // 计算总知识项数(只计算真实ID,不包括特殊标记)
- const totalItems = logs.reduce((sum, log) => {
- if (!log.retrievedItems) return sum;
- if (Array.isArray(log.retrievedItems)) {
- const realItems = log.retrievedItems.filter(id => id !== '_has_results');
- return sum + realItems.length;
- }
- return sum;
- }, 0);
- const successRate = totalLogs > 0 ? ((successfulLogs / totalLogs) * 100).toFixed(1) : 0;
-
- statsContainer.innerHTML = `
-
- 总检索次数
- ${totalLogs}
-
-
- 成功检索
- ${successfulLogs}
-
-
- 成功率
- ${successRate}%
-
-
- 检索到知识项
- ${totalItems}
-
- `;
-}
-
-// 获取相对时间
-function getTimeAgo(timeStr) {
- if (!timeStr) return '';
-
- // 处理时间字符串,支持多种格式
- let date;
- if (typeof timeStr === 'string') {
- // 首先尝试直接解析(支持RFC3339/ISO8601格式)
- date = new Date(timeStr);
-
- // 如果解析失败,尝试其他格式
- if (isNaN(date.getTime())) {
- // SQLite格式: "2006-01-02 15:04:05" 或带时区
- const sqliteMatch = timeStr.match(/(\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2}|Z)?)/);
- if (sqliteMatch) {
- let timeStr2 = sqliteMatch[1].replace(' ', 'T');
- // 如果没有时区信息,添加Z表示UTC
- if (!timeStr2.includes('Z') && !timeStr2.match(/[+-]\d{2}:\d{2}$/)) {
- timeStr2 += 'Z';
- }
- date = new Date(timeStr2);
- }
- }
-
- // 如果还是失败,尝试更宽松的格式
- if (isNaN(date.getTime())) {
- // 尝试匹配 "YYYY-MM-DD HH:MM:SS" 格式
- const match = timeStr.match(/(\d{4})-(\d{2})-(\d{2})[\sT](\d{2}):(\d{2}):(\d{2})/);
- if (match) {
- date = new Date(
- parseInt(match[1]),
- parseInt(match[2]) - 1,
- parseInt(match[3]),
- parseInt(match[4]),
- parseInt(match[5]),
- parseInt(match[6])
- );
- }
- }
- } else {
- date = new Date(timeStr);
- }
-
- // 检查日期是否有效
- if (isNaN(date.getTime())) {
- return formatTime(timeStr);
- }
-
- // 检查日期是否合理(不在1970年之前,不在未来太远)
- const year = date.getFullYear();
- if (year < 1970 || year > 2100) {
- return formatTime(timeStr);
- }
-
- const now = new Date();
- const diff = now - date;
-
- // 如果时间差为负数或过大(可能是解析错误),返回格式化时间
- if (diff < 0 || diff > 365 * 24 * 60 * 60 * 1000 * 10) { // 超过10年认为是错误
- return formatTime(timeStr);
- }
-
- const seconds = Math.floor(diff / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- const days = Math.floor(hours / 24);
-
- if (days > 0) return `${days}天前`;
- if (hours > 0) return `${hours}小时前`;
- if (minutes > 0) return `${minutes}分钟前`;
- return '刚刚';
-}
-
-// 截断ID显示
-function truncateId(id) {
- if (!id || id.length <= 16) return id;
- return id.substring(0, 8) + '...' + id.substring(id.length - 8);
-}
-
-// 筛选检索日志
-function filterRetrievalLogs() {
- const conversationId = document.getElementById('retrieval-logs-conversation-id').value.trim();
- const messageId = document.getElementById('retrieval-logs-message-id').value.trim();
- loadRetrievalLogs(conversationId, messageId);
-}
-
-// 刷新检索日志
-function refreshRetrievalLogs() {
- filterRetrievalLogs();
-}
-
-// 删除检索日志
-async function deleteRetrievalLog(id, index) {
- if (!confirm('确定要删除这条检索记录吗?')) {
- return;
- }
-
- // 找到要删除的日志卡片和删除按钮
- const logCard = document.querySelector(`.retrieval-log-card[data-index="${index}"]`);
- const deleteButton = logCard ? logCard.querySelector('.retrieval-log-delete-btn') : null;
- let originalButtonOpacity = '';
- let originalButtonDisabled = false;
-
- // 设置删除按钮的加载状态
- if (deleteButton) {
- originalButtonOpacity = deleteButton.style.opacity;
- originalButtonDisabled = deleteButton.disabled;
- deleteButton.style.opacity = '0.5';
- deleteButton.style.cursor = 'not-allowed';
- deleteButton.disabled = true;
-
- // 添加加载动画
- const svg = deleteButton.querySelector('svg');
- if (svg) {
- svg.style.animation = 'spin 1s linear infinite';
- }
- }
-
- // 立即从UI中移除该项(乐观更新)
- if (logCard) {
- logCard.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
- logCard.style.opacity = '0';
- logCard.style.transform = 'translateX(-20px)';
-
- // 等待动画完成后移除
- setTimeout(() => {
- if (logCard.parentElement) {
- logCard.remove();
-
- // 更新统计信息(临时更新,稍后会重新加载)
- updateRetrievalStatsAfterDelete();
- }
- }, 300);
- }
-
- try {
- const response = await apiFetch(`/api/knowledge/retrieval-logs/${id}`, {
- method: 'DELETE'
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.error || '删除检索日志失败');
- }
-
- // 显示成功通知
- showNotification('✅ 删除成功!检索记录已从系统中移除。', 'success');
-
- // 从内存中移除该项
- if (retrievalLogsData && index >= 0 && index < retrievalLogsData.length) {
- retrievalLogsData.splice(index, 1);
- }
-
- // 重新加载数据以确保数据同步
- const conversationId = document.getElementById('retrieval-logs-conversation-id')?.value.trim() || '';
- const messageId = document.getElementById('retrieval-logs-message-id')?.value.trim() || '';
- await loadRetrievalLogs(conversationId, messageId);
-
- } catch (error) {
- console.error('删除检索日志失败:', error);
-
- // 如果删除失败,恢复该项显示
- if (logCard) {
- logCard.style.opacity = '1';
- logCard.style.transform = '';
- logCard.style.transition = '';
- }
-
- // 恢复删除按钮状态
- if (deleteButton) {
- deleteButton.style.opacity = originalButtonOpacity || '';
- deleteButton.style.cursor = '';
- deleteButton.disabled = originalButtonDisabled;
- const svg = deleteButton.querySelector('svg');
- if (svg) {
- svg.style.animation = '';
- }
- }
-
- showNotification('❌ 删除检索日志失败: ' + error.message, 'error');
- }
-}
-
-// 临时更新统计信息(删除后)
-function updateRetrievalStatsAfterDelete() {
- const statsContainer = document.getElementById('retrieval-stats');
- if (!statsContainer) return;
-
- const allLogs = document.querySelectorAll('.retrieval-log-card');
- const totalLogs = allLogs.length;
-
- // 计算成功检索数
- const successfulLogs = Array.from(allLogs).filter(card => {
- return card.classList.contains('has-results');
- }).length;
-
- // 计算总知识项数(简化处理,实际应该从服务器获取)
- const totalItems = Array.from(allLogs).reduce((sum, card) => {
- const badge = card.querySelector('.retrieval-log-result-badge');
- if (badge && badge.classList.contains('success')) {
- const text = badge.textContent.trim();
- const match = text.match(/(\d+)\s*项/);
- if (match) {
- return sum + parseInt(match[1]);
- } else if (text === '有结果') {
- return sum + 1; // 简化处理,假设为1
- }
- }
- return sum;
- }, 0);
-
- const successRate = totalLogs > 0 ? ((successfulLogs / totalLogs) * 100).toFixed(1) : 0;
-
- statsContainer.innerHTML = `
-
- 总检索次数
- ${totalLogs}
-
-
- 成功检索
- ${successfulLogs}
-
-
- 成功率
- ${successRate}%
-
-
- 检索到知识项
- ${totalItems}
-
- `;
-}
-
-// 显示检索日志详情
-async function showRetrievalLogDetails(index) {
- if (!retrievalLogsData || index < 0 || index >= retrievalLogsData.length) {
- showNotification('无法获取检索详情', 'error');
- return;
- }
-
- const log = retrievalLogsData[index];
-
- // 获取检索到的知识项详情
- let retrievedItemsDetails = [];
- if (log.retrievedItems && Array.isArray(log.retrievedItems)) {
- const realItemIds = log.retrievedItems.filter(id => id !== '_has_results');
- if (realItemIds.length > 0) {
- try {
- // 批量获取知识项详情
- const itemPromises = realItemIds.map(async (itemId) => {
- try {
- const response = await apiFetch(`/api/knowledge/items/${itemId}`);
- if (response.ok) {
- return await response.json();
- }
- return null;
- } catch (err) {
- console.error(`获取知识项 ${itemId} 失败:`, err);
- return null;
- }
- });
-
- const items = await Promise.all(itemPromises);
- retrievedItemsDetails = items.filter(item => item !== null);
- } catch (err) {
- console.error('批量获取知识项详情失败:', err);
- }
- }
- }
-
- // 显示详情模态框
- showRetrievalLogDetailsModal(log, retrievedItemsDetails);
-}
-
-// 显示检索日志详情模态框
-function showRetrievalLogDetailsModal(log, retrievedItems) {
- // 创建或获取模态框
- let modal = document.getElementById('retrieval-log-details-modal');
- if (!modal) {
- modal = document.createElement('div');
- modal.id = 'retrieval-log-details-modal';
- modal.className = 'modal';
- modal.innerHTML = `
-
- `;
- document.body.appendChild(modal);
- }
-
- // 填充内容
- const content = document.getElementById('retrieval-log-details-content');
- const timeAgo = getTimeAgo(log.createdAt);
- const fullTime = formatTime(log.createdAt);
-
- let itemsHtml = '';
- if (retrievedItems.length > 0) {
- itemsHtml = retrievedItems.map((item, idx) => {
- // 提取内容预览
- let preview = item.content || '';
- preview = preview.replace(/^#+\s+/gm, '');
- preview = preview.replace(/```[\s\S]*?```/g, '');
- preview = preview.replace(/`[^`]+`/g, '');
- preview = preview.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1');
- preview = preview.replace(/\n+/g, ' ').replace(/\s+/g, ' ').trim();
- const previewText = preview.length > 200 ? preview.substring(0, 200) + '...' : preview;
-
- return `
-
-
-
${idx + 1}. ${escapeHtml(item.title || '未命名')}
- ${escapeHtml(item.category || '未分类')}
-
- ${item.filePath ? `
📁 ${escapeHtml(item.filePath)}
` : ''}
-
- ${escapeHtml(previewText || '无内容预览')}
-
-
- `;
- }).join('');
- } else {
- itemsHtml = '未找到知识项详情
';
- }
-
- content.innerHTML = `
-
-
-
查询信息
-
-
查询内容:
-
${escapeHtml(log.query || '无查询内容')}
-
-
-
-
-
检索信息
-
- ${log.riskType ? `
-
-
风险类型
-
${escapeHtml(log.riskType)}
-
- ` : ''}
-
-
-
检索结果
-
${retrievedItems.length} 个知识项
-
-
-
-
- ${log.conversationId || log.messageId ? `
-
-
关联信息
-
- ${log.conversationId ? `
-
-
对话ID
-
${escapeHtml(log.conversationId)}
-
- ` : ''}
- ${log.messageId ? `
-
-
消息ID
-
${escapeHtml(log.messageId)}
-
- ` : ''}
-
-
- ` : ''}
-
-
-
检索到的知识项 (${retrievedItems.length})
- ${itemsHtml}
-
-
- `;
-
- modal.style.display = 'block';
-}
-
-// 关闭检索日志详情模态框
-function closeRetrievalLogDetailsModal() {
- const modal = document.getElementById('retrieval-log-details-modal');
- if (modal) {
- modal.style.display = 'none';
- }
-}
-
-// 点击模态框外部关闭
-window.addEventListener('click', function(event) {
- const modal = document.getElementById('retrieval-log-details-modal');
- if (event.target === modal) {
- closeRetrievalLogDetailsModal();
- }
-});
-
-// 页面切换时加载数据
-if (typeof switchPage === 'function') {
- const originalSwitchPage = switchPage;
- window.switchPage = function(page) {
- originalSwitchPage(page);
-
- if (page === 'knowledge-management') {
- loadKnowledgeCategories();
- loadKnowledgeItems(knowledgePagination.currentCategory, 1, knowledgePagination.pageSize);
- updateIndexProgress(); // 更新索引进度
- } else if (page === 'knowledge-retrieval-logs') {
- loadRetrievalLogs();
- // 切换到其他页面时停止轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- } else {
- // 切换到其他页面时停止轮询
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
- }
- };
-}
-
-// 页面卸载时清理定时器
-window.addEventListener('beforeunload', function() {
- if (indexProgressInterval) {
- clearInterval(indexProgressInterval);
- indexProgressInterval = null;
- }
-});
-
-// 工具函数
-function escapeHtml(text) {
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
-}
-
-function formatTime(timeStr) {
- if (!timeStr) return '';
-
- // 处理时间字符串,支持多种格式
- let date;
- if (typeof timeStr === 'string') {
- // 首先尝试直接解析(支持RFC3339/ISO8601格式)
- date = new Date(timeStr);
-
- // 如果解析失败,尝试其他格式
- if (isNaN(date.getTime())) {
- // SQLite格式: "2006-01-02 15:04:05" 或带时区
- const sqliteMatch = timeStr.match(/(\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2}|Z)?)/);
- if (sqliteMatch) {
- let timeStr2 = sqliteMatch[1].replace(' ', 'T');
- // 如果没有时区信息,添加Z表示UTC
- if (!timeStr2.includes('Z') && !timeStr2.match(/[+-]\d{2}:\d{2}$/)) {
- timeStr2 += 'Z';
- }
- date = new Date(timeStr2);
- }
- }
-
- // 如果还是失败,尝试更宽松的格式
- if (isNaN(date.getTime())) {
- // 尝试匹配 "YYYY-MM-DD HH:MM:SS" 格式
- const match = timeStr.match(/(\d{4})-(\d{2})-(\d{2})[\sT](\d{2}):(\d{2}):(\d{2})/);
- if (match) {
- date = new Date(
- parseInt(match[1]),
- parseInt(match[2]) - 1,
- parseInt(match[3]),
- parseInt(match[4]),
- parseInt(match[5]),
- parseInt(match[6])
- );
- }
- }
- } else {
- date = new Date(timeStr);
- }
-
- // 如果日期无效,检查是否是零值时间
- if (isNaN(date.getTime())) {
- // 检查是否是零值时间的字符串形式
- if (typeof timeStr === 'string' && (timeStr.includes('0001-01-01') || timeStr.startsWith('0001'))) {
- return '';
- }
- console.warn('无法解析时间:', timeStr);
- return '';
- }
-
- // 检查日期是否合理(不在1970年之前,不在未来太远)
- const year = date.getFullYear();
- if (year < 1970 || year > 2100) {
- // 如果是零值时间(0001-01-01),返回空字符串,不显示
- if (year === 1) {
- return '';
- }
- console.warn('时间值不合理:', timeStr, '解析为:', date);
- return '';
- }
-
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- hour12: false
- });
-}
-
-// 显示通知
-function showNotification(message, type = 'info') {
- // 如果存在全局通知系统(且不是当前函数),使用它
- if (typeof window.showNotification === 'function' && window.showNotification !== showNotification) {
- window.showNotification(message, type);
- return;
- }
-
- // 否则使用自定义的toast通知
- showToastNotification(message, type);
-}
-
-// 显示Toast通知
-function showToastNotification(message, type = 'info') {
- // 创建通知容器(如果不存在)
- let container = document.getElementById('toast-notification-container');
- if (!container) {
- container = document.createElement('div');
- container.id = 'toast-notification-container';
- container.style.cssText = `
- position: fixed;
- top: 20px;
- right: 20px;
- z-index: 10000;
- display: flex;
- flex-direction: column;
- gap: 12px;
- pointer-events: none;
- `;
- document.body.appendChild(container);
- }
-
- // 创建通知元素
- const toast = document.createElement('div');
- toast.className = `toast-notification toast-${type}`;
-
- // 根据类型设置颜色
- const typeStyles = {
- success: {
- background: '#28a745',
- color: '#fff',
- icon: '✅'
- },
- error: {
- background: '#dc3545',
- color: '#fff',
- icon: '❌'
- },
- info: {
- background: '#17a2b8',
- color: '#fff',
- icon: 'ℹ️'
- },
- warning: {
- background: '#ffc107',
- color: '#000',
- icon: '⚠️'
- }
- };
-
- const style = typeStyles[type] || typeStyles.info;
-
- toast.style.cssText = `
- background: ${style.background};
- color: ${style.color};
- padding: 14px 20px;
- border-radius: 8px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- min-width: 300px;
- max-width: 500px;
- pointer-events: auto;
- animation: slideInRight 0.3s ease-out;
- display: flex;
- align-items: center;
- gap: 12px;
- font-size: 0.9375rem;
- line-height: 1.5;
- word-wrap: break-word;
- `;
-
- toast.innerHTML = `
- ${style.icon}
- ${escapeHtml(message)}
-
- `;
-
- container.appendChild(toast);
-
- // 自动移除(成功消息显示5秒,错误消息显示7秒,其他显示4秒)
- const duration = type === 'success' ? 5000 : type === 'error' ? 7000 : 4000;
- setTimeout(() => {
- if (toast.parentElement) {
- toast.style.animation = 'slideOutRight 0.3s ease-out';
- setTimeout(() => {
- if (toast.parentElement) {
- toast.remove();
- }
- }, 300);
- }
- }, duration);
-}
-
-// 添加CSS动画(如果不存在)
-if (!document.getElementById('toast-notification-styles')) {
- const style = document.createElement('style');
- style.id = 'toast-notification-styles';
- style.textContent = `
- @keyframes slideInRight {
- from {
- transform: translateX(100%);
- opacity: 0;
- }
- to {
- transform: translateX(0);
- opacity: 1;
- }
- }
- @keyframes slideOutRight {
- from {
- transform: translateX(0);
- opacity: 1;
- }
- to {
- transform: translateX(100%);
- opacity: 0;
- }
- }
- `;
- document.head.appendChild(style);
-}
-
-// 点击模态框外部关闭
-window.addEventListener('click', function(event) {
- const modal = document.getElementById('knowledge-item-modal');
- if (event.target === modal) {
- closeKnowledgeItemModal();
- }
-});
-
-// 切换到设置页面(用于功能未启用时的提示)
-function switchToSettings() {
- if (typeof switchPage === 'function') {
- switchPage('settings');
- // 等待设置页面加载后,切换到知识库配置部分
- setTimeout(() => {
- if (typeof switchSettingsSection === 'function') {
- // 查找知识库配置部分(通常在基本设置中)
- const knowledgeSection = document.querySelector('[data-section="knowledge"]');
- if (knowledgeSection) {
- switchSettingsSection('knowledge');
- } else {
- // 如果没有独立的知识库部分,切换到基本设置
- switchSettingsSection('basic');
- // 滚动到知识库配置区域
- setTimeout(() => {
- const knowledgeEnabledCheckbox = document.getElementById('knowledge-enabled');
- if (knowledgeEnabledCheckbox) {
- knowledgeEnabledCheckbox.scrollIntoView({ behavior: 'smooth', block: 'center' });
- // 高亮显示
- knowledgeEnabledCheckbox.parentElement.style.transition = 'background-color 0.3s';
- knowledgeEnabledCheckbox.parentElement.style.backgroundColor = '#e3f2fd';
- setTimeout(() => {
- knowledgeEnabledCheckbox.parentElement.style.backgroundColor = '';
- }, 2000);
- }
- }, 300);
- }
- }
- }, 100);
- }
-}
-
-// 自定义下拉组件交互
-document.addEventListener('DOMContentLoaded', function() {
- const wrapper = document.getElementById('knowledge-category-filter-wrapper');
- const trigger = document.getElementById('knowledge-category-filter-trigger');
-
- if (wrapper && trigger) {
- // 点击触发器打开/关闭下拉菜单
- trigger.addEventListener('click', function(e) {
- e.stopPropagation();
- wrapper.classList.toggle('open');
- });
-
- // 点击外部关闭下拉菜单
- document.addEventListener('click', function(e) {
- if (!wrapper.contains(e.target)) {
- wrapper.classList.remove('open');
- }
- });
-
- // 选择选项时更新选中状态
- const dropdown = document.getElementById('knowledge-category-filter-dropdown');
- if (dropdown) {
- // 默认选中"全部"选项
- const defaultOption = dropdown.querySelector('.custom-select-option[data-value=""]');
- if (defaultOption) {
- defaultOption.classList.add('selected');
- }
-
- dropdown.addEventListener('click', function(e) {
- const option = e.target.closest('.custom-select-option');
- if (option) {
- // 移除之前的选中状态
- dropdown.querySelectorAll('.custom-select-option').forEach(opt => {
- opt.classList.remove('selected');
- });
- // 添加选中状态
- option.classList.add('selected');
- }
- });
- }
- }
-});
-