Add files via upload

This commit is contained in:
公明
2025-12-20 18:06:51 +08:00
committed by GitHub
parent d6767c5d46
commit 287ad0ec09
4 changed files with 181 additions and 0 deletions

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -928,6 +928,12 @@ function renderRetrievalLogs(logs) {
</svg>
查看详情
</button>
<button class="btn-secondary btn-sm retrieval-log-delete-btn" onclick="deleteRetrievalLog('${escapeHtml(log.id)}', ${index})" style="margin-top: 12px; margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: var(--error-color, #dc3545); border-color: var(--error-color, #dc3545);" onmouseover="this.style.backgroundColor='rgba(220, 53, 69, 0.1)'; this.style.color='#dc3545';" onmouseout="this.style.backgroundColor=''; this.style.color='var(--error-color, #dc3545)';" title="删除">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
删除
</button>
</div>
</div>
</div>
@@ -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 = `
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">总检索次数</span>
<span class="retrieval-stat-value">${totalLogs}</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">成功检索</span>
<span class="retrieval-stat-value text-success">${successfulLogs}</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">成功率</span>
<span class="retrieval-stat-value">${successRate}%</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">检索到知识项</span>
<span class="retrieval-stat-value">${totalItems}</span>
</div>
`;
}
// 显示检索日志详情
async function showRetrievalLogDetails(index) {
if (!retrievalLogsData || index < 0 || index >= retrievalLogsData.length) {