feat: 新增FAQ列表排序功能,支持按更新时间升序/降序排列;优化标签名称批量查询,提升查询效率

This commit is contained in:
wizardchen
2025-12-24 20:47:08 +08:00
committed by lyingbug
parent c11b3e5b00
commit 8430b9fa2d
14 changed files with 88 additions and 9 deletions
+6 -1
View File
@@ -17,6 +17,7 @@ type FAQEntry struct {
KnowledgeID string `json:"knowledge_id"`
KnowledgeBaseID string `json:"knowledge_base_id"`
TagID string `json:"tag_id"`
TagName string `json:"tag_name"`
IsEnabled bool `json:"is_enabled"`
IsRecommended bool `json:"is_recommended"`
StandardQuestion string `json:"standard_question"`
@@ -139,8 +140,9 @@ type faqSimpleResponse struct {
}
// ListFAQEntries returns paginated FAQ entries under a knowledge base.
// sortOrder: "asc" for time ascending (updated_at ASC), default is time descending (updated_at DESC)
func (c *Client) ListFAQEntries(ctx context.Context,
knowledgeBaseID string, page, pageSize int, tagID string, keyword string,
knowledgeBaseID string, page, pageSize int, tagID string, keyword string, sortOrder string,
) (*FAQEntriesPage, error) {
path := fmt.Sprintf("/api/v1/knowledge-bases/%s/faq/entries", knowledgeBaseID)
query := url.Values{}
@@ -156,6 +158,9 @@ func (c *Client) ListFAQEntries(ctx context.Context,
if keyword != "" {
query.Add("keyword", keyword)
}
if sortOrder != "" {
query.Add("sort_order", sortOrder)
}
resp, err := c.doRequest(ctx, http.MethodGet, path, nil, query)
if err != nil {
+1 -1
View File
@@ -139,7 +139,7 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args map[string]inter
ListPagedChunksByKnowledgeID(ctx, t.tenantID, id, &types.Pagination{
Page: 1,
PageSize: 1000,
}, []types.ChunkType{"text"}, "", "")
}, []types.ChunkType{"text"}, "", "", "")
if err != nil {
mu.Lock()
results[id] = &docInfo{
+1 -1
View File
@@ -1052,7 +1052,7 @@ func (t *KnowledgeSearchTool) formatOutput(
_, total, err := t.chunkService.GetRepository().ListPagedChunksByKnowledgeID(ctx,
t.tenantID, result.KnowledgeID,
&types.Pagination{Page: 1, PageSize: 1},
[]types.ChunkType{types.ChunkTypeText}, "", "",
[]types.ChunkType{types.ChunkTypeText}, "", "", "",
)
if err != nil {
logger.Warnf(
@@ -117,7 +117,7 @@ func (t *ListKnowledgeChunksTool) Execute(ctx context.Context, args map[string]i
}
chunks, total, err := t.chunkService.GetRepository().ListPagedChunksByKnowledgeID(ctx,
t.tenantID, knowledgeID, pagination, []types.ChunkType{types.ChunkTypeText, types.ChunkTypeFAQ}, "", "")
t.tenantID, knowledgeID, pagination, []types.ChunkType{types.ChunkTypeText, types.ChunkTypeFAQ}, "", "", "")
if err != nil {
return &types.ToolResult{
Success: false,
+8 -1
View File
@@ -78,6 +78,7 @@ func (r *chunkRepository) ListPagedChunksByKnowledgeID(
chunkType []types.ChunkType,
tagID string,
keyword string,
sortOrder string,
) ([]*types.Chunk, int64, error) {
var chunks []*types.Chunk
var total int64
@@ -109,8 +110,14 @@ func (r *chunkRepository) ListPagedChunksByKnowledgeID(
// Then query the paginated data
dataQuery := baseFilter(r.db.WithContext(ctx))
// Default is time descending, "asc" for time ascending
orderClause := "updated_at DESC"
if sortOrder == "asc" {
orderClause = "updated_at ASC"
}
if err := dataQuery.
Order("chunk_index ASC").
Order(orderClause).
Offset(page.Offset()).
Limit(page.Limit()).
Find(&chunks).Error; err != nil {
+14
View File
@@ -40,6 +40,20 @@ func (r *knowledgeTagRepository) GetByID(ctx context.Context, tenantID uint64, i
return &tag, nil
}
// GetByIDs retrieves multiple tags by their IDs in a single query
func (r *knowledgeTagRepository) GetByIDs(ctx context.Context, tenantID uint64, ids []string) ([]*types.KnowledgeTag, error) {
if len(ids) == 0 {
return []*types.KnowledgeTag{}, nil
}
var tags []*types.KnowledgeTag
if err := r.db.WithContext(ctx).
Where("tenant_id = ? AND id IN (?)", tenantID, ids).
Find(&tags).Error; err != nil {
return nil, err
}
return tags, nil
}
// GetByName gets a knowledge tag by name
func (r *knowledgeTagRepository) GetByName(ctx context.Context, tenantID uint64, kbID string, name string) (*types.KnowledgeTag, error) {
var tag types.KnowledgeTag
@@ -337,7 +337,7 @@ func (s *agentService) getKnowledgeBaseInfos(ctx context.Context, kbIDs []string
pageResult, err := s.knowledgeService.ListFAQEntries(ctx, kbID, &types.Pagination{
Page: 1,
PageSize: 10,
}, "", "")
}, "", "", "")
if err == nil && pageResult != nil {
docCount = int(pageResult.Total)
if entries, ok := pageResult.Data.([]*types.FAQEntry); ok {
+1
View File
@@ -151,6 +151,7 @@ func (s *chunkService) ListPagedChunksByKnowledgeID(ctx context.Context,
chunkType,
"",
"",
"",
)
if err != nil {
logger.ErrorWithFields(ctx, err, map[string]interface{}{
+45 -2
View File
@@ -2548,6 +2548,7 @@ func (s *knowledgeService) CloneChunk(ctx context.Context, src, dst *types.Knowl
chunkType,
"",
"",
"",
)
chunkPage++
if err != nil {
@@ -2642,7 +2643,7 @@ func (s *knowledgeService) CloneChunk(ctx context.Context, src, dst *types.Knowl
// ListFAQEntries lists FAQ entries under a FAQ knowledge base.
func (s *knowledgeService) ListFAQEntries(ctx context.Context,
kbID string, page *types.Pagination, tagID string, keyword string,
kbID string, page *types.Pagination, tagID string, keyword string, sortOrder string,
) (*types.PageResult, error) {
if page == nil {
page = &types.Pagination{}
@@ -2662,11 +2663,33 @@ func (s *knowledgeService) ListFAQEntries(ctx context.Context,
}
chunkType := []types.ChunkType{types.ChunkTypeFAQ}
chunks, total, err := s.chunkRepo.ListPagedChunksByKnowledgeID(
ctx, tenantID, faqKnowledge.ID, page, chunkType, tagID, keyword,
ctx, tenantID, faqKnowledge.ID, page, chunkType, tagID, keyword, sortOrder,
)
if err != nil {
return nil, err
}
// Build tag ID to name mapping for all unique tag IDs (batch query)
tagNameMap := make(map[string]string)
tagIDs := make([]string, 0)
tagIDSet := make(map[string]struct{})
for _, chunk := range chunks {
if chunk.TagID != "" {
if _, exists := tagIDSet[chunk.TagID]; !exists {
tagIDSet[chunk.TagID] = struct{}{}
tagIDs = append(tagIDs, chunk.TagID)
}
}
}
if len(tagIDs) > 0 {
tags, err := s.tagRepo.GetByIDs(ctx, tenantID, tagIDs)
if err == nil {
for _, tag := range tags {
tagNameMap[tag.ID] = tag.Name
}
}
}
kb.EnsureDefaults()
entries := make([]*types.FAQEntry, 0, len(chunks))
for _, chunk := range chunks {
@@ -2674,6 +2697,10 @@ func (s *knowledgeService) ListFAQEntries(ctx context.Context,
if err != nil {
return nil, err
}
// Set tag name from mapping
if entry.TagID != "" {
entry.TagName = tagNameMap[entry.TagID]
}
entries = append(entries, entry)
}
return types.NewPageResult(total, page, entries), nil
@@ -3315,6 +3342,14 @@ func (s *knowledgeService) CreateFAQEntry(ctx context.Context,
return nil, err
}
// 查询TagName
if entry.TagID != "" {
tag, tagErr := s.tagRepo.GetByID(ctx, tenantID, entry.TagID)
if tagErr == nil && tag != nil {
entry.TagName = tag.Name
}
}
return entry, nil
}
@@ -3356,6 +3391,14 @@ func (s *knowledgeService) GetFAQEntry(ctx context.Context,
return nil, err
}
// 查询TagName
if entry.TagID != "" {
tag, tagErr := s.tagRepo.GetByID(ctx, tenantID, entry.TagID)
if tagErr == nil && tag != nil {
entry.TagName = tag.Name
}
}
return entry, nil
}
+3 -1
View File
@@ -33,6 +33,7 @@ func NewFAQHandler(knowledgeService interfaces.KnowledgeService) *FAQHandler {
// @Param page_size query int false "每页数量"
// @Param tag_id query string false "标签ID筛选"
// @Param keyword query string false "关键词搜索"
// @Param sort_order query string false "排序方式: asc(按更新时间正序), 默认按更新时间倒序"
// @Success 200 {object} map[string]interface{} "FAQ列表"
// @Failure 400 {object} errors.AppError "请求参数错误"
// @Security Bearer
@@ -49,8 +50,9 @@ func (h *FAQHandler) ListEntries(c *gin.Context) {
tagID := secutils.SanitizeForLog(c.Query("tag_id"))
keyword := secutils.SanitizeForLog(c.Query("keyword"))
sortOrder := secutils.SanitizeForLog(c.Query("sort_order"))
result, err := h.knowledgeService.ListFAQEntries(ctx, secutils.SanitizeForLog(c.Param("id")), &page, tagID, keyword)
result, err := h.knowledgeService.ListFAQEntries(ctx, secutils.SanitizeForLog(c.Param("id")), &page, tagID, keyword, sortOrder)
if err != nil {
logger.ErrorWithFields(ctx, err, nil)
c.Error(err)
+1
View File
@@ -180,6 +180,7 @@ type FAQEntry struct {
KnowledgeID string `json:"knowledge_id"`
KnowledgeBaseID string `json:"knowledge_base_id"`
TagID string `json:"tag_id"`
TagName string `json:"tag_name"`
IsEnabled bool `json:"is_enabled"`
IsRecommended bool `json:"is_recommended"`
StandardQuestion string `json:"standard_question"`
+2
View File
@@ -18,6 +18,7 @@ type ChunkRepository interface {
ListChunksByKnowledgeID(ctx context.Context, tenantID uint64, knowledgeID string) ([]*types.Chunk, error)
// ListPagedChunksByKnowledgeID lists paged chunks by knowledge id.
// When tagID is non-empty, results are filtered by tag_id.
// sortOrder: "asc" for time ascending (updated_at ASC), default is time descending (updated_at DESC)
ListPagedChunksByKnowledgeID(
ctx context.Context,
tenantID uint64,
@@ -26,6 +27,7 @@ type ChunkRepository interface {
chunkType []types.ChunkType,
tagID string,
keyword string,
sortOrder string,
) ([]*types.Chunk, int64, error)
ListChunkByParentID(ctx context.Context, tenantID uint64, parentID string) ([]*types.Chunk, error)
// UpdateChunk updates a chunk
+2
View File
@@ -74,12 +74,14 @@ type KnowledgeService interface {
UpdateImageInfo(ctx context.Context, knowledgeID string, chunkID string, imageInfo string) error
// ListFAQEntries lists FAQ entries under a FAQ knowledge base.
// When tagID is non-empty, results are filtered by tag_id on FAQ chunks.
// sortOrder: "asc" for time ascending (updated_at ASC), default is time descending (updated_at DESC)
ListFAQEntries(
ctx context.Context,
kbID string,
page *types.Pagination,
tagID string,
keyword string,
sortOrder string,
) (*types.PageResult, error)
// UpsertFAQEntries imports or appends FAQ entries asynchronously.
// Returns task ID (Knowledge ID) for tracking import progress.
+2
View File
@@ -26,6 +26,8 @@ type KnowledgeTagRepository interface {
Create(ctx context.Context, tag *types.KnowledgeTag) error
Update(ctx context.Context, tag *types.KnowledgeTag) error
GetByID(ctx context.Context, tenantID uint64, id string) (*types.KnowledgeTag, error)
// GetByIDs retrieves multiple tags by their IDs in a single query.
GetByIDs(ctx context.Context, tenantID uint64, ids []string) ([]*types.KnowledgeTag, error)
GetByName(ctx context.Context, tenantID uint64, kbID string, name string) (*types.KnowledgeTag, error)
ListByKB(
ctx context.Context,