mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-09-24 16:29:01 +08:00
feat: 新增FAQ列表排序功能,支持按更新时间升序/降序排列;优化标签名称批量查询,提升查询效率
This commit is contained in:
+6
-1
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -151,6 +151,7 @@ func (s *chunkService) ListPagedChunksByKnowledgeID(ctx context.Context,
|
||||
chunkType,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user