refactor(knowledge-base): address review feedback

Backend
- BatchDeleteKnowledge handler now enqueues an asynq
  TypeKnowledgeListDelete task instead of calling DeleteKnowledgeList
  synchronously, matching the pattern used by ClearKnowledgeBaseContents.
  Avoids long HTTP timeouts on large batches and shares the existing
  async cleanup pipeline.
- Extract a small enqueueKnowledgeListDelete helper on KnowledgeHandler
  so BatchDeleteKnowledge and ClearKnowledgeBaseContents share the
  payload/marshal/enqueue boilerplate.

Frontend (DocumentListView)
- softer outer border and lighter row separators
- header background switched to the page tone with placeholder-color
  text, reducing institutional gray
- selected row uses an inset left accent bar and a quieter brand tint
  instead of a saturated background; hover stays neutral
- consolidate the .row-more-btn { opacity: 1 } rule that was duplicated
  across the hover and selected blocks
This commit is contained in:
nullkey
2026-04-29 12:01:38 +08:00
committed by lyingbug
parent be8e53bf08
commit 48a764b866
2 changed files with 58 additions and 35 deletions
@@ -265,8 +265,8 @@ const handleAction = (action: 'edit' | 'reparse' | 'move' | 'delete', item: Know
flex-direction: column;
width: 100%;
background: var(--td-bg-color-container, #fff);
border: 1px solid var(--td-component-border, #e7e7e7);
border-radius: 10px;
border: 1px solid var(--td-component-stroke, #f0f0f0);
border-radius: 8px;
overflow: hidden;
}
@@ -291,12 +291,13 @@ const handleAction = (action: 'edit' | 'reparse' | 'move' | 'delete', item: Know
position: sticky;
top: 0;
z-index: 2;
height: 40px;
height: 36px;
font-size: 12px;
font-weight: 500;
color: var(--td-text-color-secondary, #888);
background: var(--td-bg-color-secondarycontainer, #fafafa);
border-bottom: 1px solid var(--td-component-border, #e7e7e7);
letter-spacing: 0.02em;
color: var(--td-text-color-placeholder, #a6a6a6);
background: var(--td-bg-color-page, #fafbfc);
border-bottom: 1px solid var(--td-component-stroke, #f0f0f0);
}
.doc-list-body {
@@ -305,25 +306,31 @@ const handleAction = (action: 'edit' | 'reparse' | 'move' | 'delete', item: Know
}
.doc-list-row {
position: relative;
height: 48px;
font-size: 13px;
color: var(--td-text-color-primary, #232323);
border-bottom: 1px solid var(--td-component-stroke, #f0f0f0);
border-bottom: 1px solid var(--td-component-stroke, #f3f3f3);
cursor: pointer;
transition: background-color 0.12s ease;
transition: background-color 0.12s ease, box-shadow 0.12s ease;
&:last-child { border-bottom: 0; }
&:hover,
&.menu-open {
background: var(--td-bg-color-container-hover, #f6f8fa);
.row-more-btn { opacity: 1; }
&:hover:not(.selected),
&.menu-open:not(.selected) {
background: var(--td-bg-color-page, #f7f8fa);
}
&.selected {
background: var(--td-brand-color-1, #f0f6ff);
&:hover { background: var(--td-brand-color-2, #e6f0ff); }
background: var(--td-brand-color-1, #f2f5fc);
box-shadow: inset 3px 0 0 var(--td-brand-color, #0052d9);
&:hover { background: var(--td-brand-color-light, #e8eefc); }
}
&:hover .row-more-btn,
&.menu-open .row-more-btn,
&.selected .row-more-btn { opacity: 1; }
}
.cell {
+37 -21
View File
@@ -187,6 +187,29 @@ func (h *KnowledgeHandler) handleDuplicateKnowledgeError(c *gin.Context,
return false
}
// enqueueKnowledgeListDelete enqueues an async batch-delete task for the
// given knowledge IDs and returns the asynq task ID.
func (h *KnowledgeHandler) enqueueKnowledgeListDelete(
ctx context.Context, tenantID uint64, ids []string,
) (string, error) {
payload := types.KnowledgeListDeletePayload{
TenantID: tenantID,
KnowledgeIDs: ids,
}
langfuse.InjectTracing(ctx, &payload)
payloadBytes, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("marshal payload: %w", err)
}
task := asynq.NewTask(types.TypeKnowledgeListDelete, payloadBytes,
asynq.Queue("low"), asynq.MaxRetry(3))
info, err := h.asynqClient.Enqueue(task)
if err != nil {
return "", fmt.Errorf("enqueue task: %w", err)
}
return info.ID, nil
}
// CreateKnowledgeFromFile godoc
// @Summary 从文件创建知识
// @Description 上传文件并创建知识条目
@@ -715,16 +738,23 @@ func (h *KnowledgeHandler) BatchDeleteKnowledge(c *gin.Context) {
}
}
logger.Infof(ctx, "Batch deleting %d knowledge entries from KB %s", len(ids), secutils.SanitizeForLog(kbID))
if err := h.kgService.DeleteKnowledgeList(ctx, ids); err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(errors.NewInternalServerError(err.Error()))
taskID, err := h.enqueueKnowledgeListDelete(ctx, effectiveTenantID, ids)
if err != nil {
logger.Errorf(ctx, "Failed to enqueue batch knowledge delete task: %v", err)
c.Error(errors.NewInternalServerError("Failed to enqueue batch delete task"))
return
}
logger.Infof(ctx, "Batch knowledge delete task enqueued: %s, kb_id: %s, count: %d",
taskID, secutils.SanitizeForLog(kbID), len(ids))
c.JSON(http.StatusOK, gin.H{
"success": true,
"deleted": len(ids),
"message": "Batch delete task submitted",
"data": gin.H{
"task_id": taskID,
"deleted_count": len(ids),
},
})
}
@@ -782,21 +812,7 @@ func (h *KnowledgeHandler) ClearKnowledgeBaseContents(c *gin.Context) {
knowledgeIDs = append(knowledgeIDs, knowledge.ID)
}
payload := types.KnowledgeListDeletePayload{
TenantID: effectiveTenantID,
KnowledgeIDs: knowledgeIDs,
}
langfuse.InjectTracing(ctx, &payload)
payloadBytes, err := json.Marshal(payload)
if err != nil {
logger.Errorf(ctx, "Failed to marshal knowledge list delete payload: %v", err)
c.Error(errors.NewInternalServerError("Failed to create cleanup task"))
return
}
task := asynq.NewTask(types.TypeKnowledgeListDelete, payloadBytes,
asynq.Queue("low"), asynq.MaxRetry(3))
info, err := h.asynqClient.Enqueue(task)
taskID, err := h.enqueueKnowledgeListDelete(ctx, effectiveTenantID, knowledgeIDs)
if err != nil {
logger.Errorf(ctx, "Failed to enqueue knowledge list delete task: %v", err)
c.Error(errors.NewInternalServerError("Failed to enqueue cleanup task"))
@@ -804,7 +820,7 @@ func (h *KnowledgeHandler) ClearKnowledgeBaseContents(c *gin.Context) {
}
logger.Infof(ctx, "Knowledge base contents clear task enqueued: %s, kb_id: %s, count: %d",
info.ID, secutils.SanitizeForLog(kbID), len(knowledgeIDs))
taskID, secutils.SanitizeForLog(kbID), len(knowledgeIDs))
c.JSON(http.StatusOK, gin.H{
"success": true,