From 287ad0ec092d892a6b5ed4d2c4e61f42531813fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:06:51 +0800 Subject: [PATCH] Add files via upload --- internal/app/app.go | 1 + internal/handler/knowledge.go | 13 +++ internal/knowledge/manager.go | 19 +++++ web/static/js/knowledge.js | 148 ++++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+) diff --git a/internal/app/app.go b/internal/app/app.go index 1285b187..26987145 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -396,6 +396,7 @@ func setupRoutes( protected.POST("/knowledge/index", knowledgeHandler.RebuildIndex) protected.POST("/knowledge/scan", knowledgeHandler.ScanKnowledgeBase) protected.GET("/knowledge/retrieval-logs", knowledgeHandler.GetRetrievalLogs) + protected.DELETE("/knowledge/retrieval-logs/:id", knowledgeHandler.DeleteRetrievalLog) protected.POST("/knowledge/search", knowledgeHandler.Search) } diff --git a/internal/handler/knowledge.go b/internal/handler/knowledge.go index 020f6fd9..1dd7feb1 100644 --- a/internal/handler/knowledge.go +++ b/internal/handler/knowledge.go @@ -209,6 +209,19 @@ func (h *KnowledgeHandler) GetRetrievalLogs(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"logs": logs}) } +// DeleteRetrievalLog 删除检索日志 +func (h *KnowledgeHandler) DeleteRetrievalLog(c *gin.Context) { + id := c.Param("id") + + if err := h.manager.DeleteRetrievalLog(id); err != nil { + h.logger.Error("删除检索日志失败", zap.Error(err)) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "删除成功"}) +} + // GetIndexStatus 获取索引状态 func (h *KnowledgeHandler) GetIndexStatus(c *gin.Context) { status, err := h.manager.GetIndexStatus() diff --git a/internal/knowledge/manager.go b/internal/knowledge/manager.go index e4a4cf2b..e62c9143 100644 --- a/internal/knowledge/manager.go +++ b/internal/knowledge/manager.go @@ -445,3 +445,22 @@ func (m *Manager) GetRetrievalLogs(conversationID, messageID string, limit int) return logs, nil } +// DeleteRetrievalLog 删除检索日志 +func (m *Manager) DeleteRetrievalLog(id string) error { + result, err := m.db.Exec("DELETE FROM knowledge_retrieval_logs WHERE id = ?", id) + if err != nil { + return fmt.Errorf("删除检索日志失败: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("获取删除行数失败: %w", err) + } + + if rowsAffected == 0 { + return fmt.Errorf("检索日志不存在") + } + + return nil +} + diff --git a/web/static/js/knowledge.js b/web/static/js/knowledge.js index 1c067288..6926ebca 100644 --- a/web/static/js/knowledge.js +++ b/web/static/js/knowledge.js @@ -928,6 +928,12 @@ function renderRetrievalLogs(logs) { 查看详情 + @@ -1072,6 +1078,148 @@ 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) {