mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-09-24 16:29:01 +08:00
refactor: 简化FAQ导入结果获取逻辑
将导入结果字段合并到FAQImportProgress结构体中,当状态为completed时自动填充。删除了独立的GetLastFAQImportResult接口和相关方法,客户端改为通过进度接口获取已完成任务的结果,并使用localStorage记录最后完成的任务ID。
This commit is contained in:
+8
-66
@@ -358,6 +358,7 @@ type FAQFailedEntry struct {
|
||||
}
|
||||
|
||||
// FAQImportProgress represents the progress of an async FAQ import task.
|
||||
// When Status is "completed", the result fields (SkippedCount, ImportMode, ImportedAt, DisplayStatus, ProcessingTime) are populated.
|
||||
type FAQImportProgress struct {
|
||||
TaskID string `json:"task_id"`
|
||||
KBID string `json:"kb_id"`
|
||||
@@ -368,6 +369,7 @@ type FAQImportProgress struct {
|
||||
Processed int `json:"processed"`
|
||||
SuccessCount int `json:"success_count"`
|
||||
FailedCount int `json:"failed_count"`
|
||||
SkippedCount int `json:"skipped_count,omitempty"`
|
||||
FailedEntries []FAQFailedEntry `json:"failed_entries,omitempty"`
|
||||
FailedEntriesURL string `json:"failed_entries_url,omitempty"` // CSV download URL when too many failures
|
||||
Message string `json:"message"`
|
||||
@@ -375,6 +377,12 @@ type FAQImportProgress struct {
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
DryRun bool `json:"dry_run,omitempty"` // Whether this is a dry run validation
|
||||
|
||||
// Result fields (populated when Status == "completed")
|
||||
ImportMode string `json:"import_mode,omitempty"`
|
||||
ImportedAt time.Time `json:"imported_at,omitempty"`
|
||||
DisplayStatus string `json:"display_status,omitempty"`
|
||||
ProcessingTime int64 `json:"processing_time,omitempty"`
|
||||
}
|
||||
|
||||
// FAQImportProgressResponse wraps the FAQ import progress response.
|
||||
@@ -400,69 +408,3 @@ func (c *Client) GetFAQImportProgress(ctx context.Context, taskID string) (*FAQI
|
||||
}
|
||||
return response.Data, nil
|
||||
}
|
||||
|
||||
// FAQImportResult stores statistics of the last FAQ import.
|
||||
// It matches server response from GET /knowledge-bases/{id}/faq/import/last-result.
|
||||
type FAQImportResult struct {
|
||||
TotalEntries int `json:"total_entries"`
|
||||
SuccessCount int `json:"success_count"`
|
||||
FailedCount int `json:"failed_count"`
|
||||
SkippedCount int `json:"skipped_count"`
|
||||
|
||||
ImportMode string `json:"import_mode"`
|
||||
ImportedAt time.Time `json:"imported_at"`
|
||||
TaskID string `json:"task_id"`
|
||||
|
||||
FailedEntriesURL string `json:"failed_entries_url,omitempty"`
|
||||
|
||||
DisplayStatus string `json:"display_status"`
|
||||
ProcessingTime int64 `json:"processing_time"`
|
||||
}
|
||||
|
||||
type faqImportResultResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data *FAQImportResult `json:"data"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
type updateLastFAQImportResultDisplayStatusRequest struct {
|
||||
DisplayStatus string `json:"display_status"`
|
||||
}
|
||||
|
||||
// GetLastFAQImportResult retrieves the last FAQ import result summary for a knowledge base.
|
||||
func (c *Client) GetLastFAQImportResult(ctx context.Context, knowledgeBaseID string) (*FAQImportResult, error) {
|
||||
path := fmt.Sprintf("/api/v1/knowledge-bases/%s/faq/import/last-result", knowledgeBaseID)
|
||||
resp, err := c.doRequest(ctx, http.MethodGet, path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var response faqImportResultResponse
|
||||
if err := parseResponse(resp, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Data, nil
|
||||
}
|
||||
|
||||
// UpdateLastFAQImportResultDisplayStatus updates the display status (open/close) of the last FAQ import result.
|
||||
func (c *Client) UpdateLastFAQImportResultDisplayStatus(ctx context.Context, knowledgeBaseID string, displayStatus string) error {
|
||||
path := fmt.Sprintf("/api/v1/knowledge-bases/%s/faq/import/last-result/display", knowledgeBaseID)
|
||||
resp, err := c.doRequest(ctx, http.MethodPut, path, &updateLastFAQImportResultDisplayStatusRequest{DisplayStatus: displayStatus}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var response faqSimpleResponse
|
||||
return parseResponse(resp, &response)
|
||||
}
|
||||
|
||||
// GetFAQImportResult is kept for backward compatibility; it now calls GetLastFAQImportResult.
|
||||
func (c *Client) GetFAQImportResult(ctx context.Context, knowledgeBaseID string) (*FAQImportResult, error) {
|
||||
return c.GetLastFAQImportResult(ctx, knowledgeBaseID)
|
||||
}
|
||||
|
||||
// UpdateFAQImportResultDisplayStatus is kept for backward compatibility; it now calls UpdateLastFAQImportResultDisplayStatus.
|
||||
func (c *Client) UpdateFAQImportResultDisplayStatus(ctx context.Context, knowledgeBaseID string, displayStatus string) error {
|
||||
return c.UpdateLastFAQImportResultDisplayStatus(ctx, knowledgeBaseID, displayStatus)
|
||||
}
|
||||
|
||||
@@ -232,10 +232,6 @@ export function getFAQImportProgress(taskId: string) {
|
||||
return get(`/api/v1/faq/import/progress/${taskId}`);
|
||||
}
|
||||
|
||||
export function getFAQImportResult(knowledgeBaseId: string) {
|
||||
return get(`/api/v1/knowledge-bases/${knowledgeBaseId}/faq/import/last-result`);
|
||||
}
|
||||
|
||||
export function updateFAQImportResultDisplayStatus(knowledgeBaseId: string, displayStatus: 'open' | 'close') {
|
||||
return put(`/api/v1/knowledge-bases/${knowledgeBaseId}/faq/import/last-result/display`, {
|
||||
display_status: displayStatus
|
||||
|
||||
@@ -1316,7 +1316,6 @@ import {
|
||||
getKnowledgeBaseById,
|
||||
listKnowledgeBases,
|
||||
getFAQImportProgress,
|
||||
getFAQImportResult,
|
||||
updateFAQImportResultDisplayStatus,
|
||||
} from '@/api/knowledge-base'
|
||||
import * as XLSX from 'xlsx'
|
||||
@@ -2451,6 +2450,10 @@ const startPolling = (taskId: string) => {
|
||||
if (status === 'success' || status === 'failed') {
|
||||
stopPolling()
|
||||
if (status === 'success') {
|
||||
// 保存已完成的 taskId 用于后续加载结果
|
||||
if (importState.taskId) {
|
||||
saveLastCompletedTaskId(importState.taskId)
|
||||
}
|
||||
MessagePlugin.success(t('knowledgeEditor.faqImport.importSuccess'))
|
||||
// 清除筛选条件,确保用户能看到所有新导入的数据
|
||||
selectedTagId.value = ''
|
||||
@@ -2588,13 +2591,61 @@ const restoreImportTask = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// localStorage key for last completed task
|
||||
const getLastCompletedTaskKey = () => {
|
||||
return `faq_import_last_completed_${props.kbId}`
|
||||
}
|
||||
|
||||
const saveLastCompletedTaskId = (taskId: string) => {
|
||||
if (!props.kbId) return
|
||||
try {
|
||||
localStorage.setItem(getLastCompletedTaskKey(), taskId)
|
||||
} catch (error) {
|
||||
console.error('Failed to save last completed taskId:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getLastCompletedTaskId = (): string | null => {
|
||||
if (!props.kbId) return null
|
||||
try {
|
||||
return localStorage.getItem(getLastCompletedTaskKey())
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 加载持久化的导入结果统计
|
||||
const loadImportResult = async () => {
|
||||
if (!props.kbId) return
|
||||
|
||||
const lastTaskId = getLastCompletedTaskId()
|
||||
if (!lastTaskId) {
|
||||
importResult.value = null
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res: any = await getFAQImportResult(props.kbId)
|
||||
if (res?.data) {
|
||||
importResult.value = res.data
|
||||
const res: any = await getFAQImportProgress(lastTaskId)
|
||||
const data = res?.data
|
||||
if (data && data.status === 'completed') {
|
||||
// 检查后端返回的 display_status,如果是 close 则不显示
|
||||
if (data.display_status === 'close') {
|
||||
importResult.value = null
|
||||
return
|
||||
}
|
||||
// Map progress fields to importResult format
|
||||
importResult.value = {
|
||||
total_entries: data.total,
|
||||
success_count: data.success_count,
|
||||
failed_count: data.failed_count,
|
||||
skipped_count: data.skipped_count || 0,
|
||||
import_mode: data.import_mode || 'append',
|
||||
imported_at: data.imported_at,
|
||||
task_id: data.task_id,
|
||||
failed_entries_url: data.failed_entries_url,
|
||||
display_status: data.display_status || 'open',
|
||||
processing_time: data.processing_time || 0,
|
||||
}
|
||||
} else {
|
||||
importResult.value = null
|
||||
}
|
||||
|
||||
@@ -6095,40 +6095,23 @@ func (s *knowledgeService) GetFAQImportProgress(ctx context.Context, taskID stri
|
||||
if err := json.Unmarshal(data, &progress); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal FAQ import progress: %w", err)
|
||||
}
|
||||
return &progress, nil
|
||||
}
|
||||
|
||||
// GetLastFAQImportResult retrieves the latest FAQ import result for a knowledge base
|
||||
func (s *knowledgeService) GetLastFAQImportResult(ctx context.Context, kbID string) (*types.FAQImportResult, error) {
|
||||
// 获取当前租户ID
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint64)
|
||||
|
||||
// 查找FAQ类型的knowledge
|
||||
knowledgeList, err := s.repo.ListKnowledgeByKnowledgeBaseID(ctx, tenantID, kbID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list knowledge: %w", err)
|
||||
}
|
||||
|
||||
// 查找FAQ类型的knowledge
|
||||
var faqKnowledge *types.Knowledge
|
||||
for _, k := range knowledgeList {
|
||||
if k.Type == types.KnowledgeTypeFAQ {
|
||||
faqKnowledge = k
|
||||
break
|
||||
// If task is completed, enrich with persisted result fields from database
|
||||
if progress.Status == types.FAQImportStatusCompleted && progress.KnowledgeID != "" {
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint64)
|
||||
knowledge, err := s.repo.GetKnowledgeByID(ctx, tenantID, progress.KnowledgeID)
|
||||
if err == nil && knowledge != nil {
|
||||
if result, err := knowledge.GetLastFAQImportResult(); err == nil && result != nil {
|
||||
progress.SkippedCount = result.SkippedCount
|
||||
progress.ImportMode = result.ImportMode
|
||||
progress.ImportedAt = result.ImportedAt
|
||||
progress.DisplayStatus = result.DisplayStatus
|
||||
progress.ProcessingTime = result.ProcessingTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if faqKnowledge == nil {
|
||||
return nil, werrors.NewNotFoundError("FAQ knowledge not found in this knowledge base")
|
||||
}
|
||||
|
||||
// 解析导入结果
|
||||
result, err := faqKnowledge.GetLastFAQImportResult()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse FAQ import result: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return &progress, nil
|
||||
}
|
||||
|
||||
// UpdateLastFAQImportResultDisplayStatus updates the display status of FAQ import result
|
||||
|
||||
@@ -423,44 +423,6 @@ func (h *FAQHandler) GetImportProgress(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetLastImportResult godoc
|
||||
// @Summary 获取FAQ最后一次导入结果统计
|
||||
// @Description 获取FAQ知识库的最后一次导入结果统计信息
|
||||
// @Tags FAQ管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "知识库ID"
|
||||
// @Success 200 {object} map[string]interface{} "导入结果统计"
|
||||
// @Failure 404 {object} errors.AppError "知识库不存在或无导入记录"
|
||||
// @Security Bearer
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /knowledge-bases/{id}/faq/import/last-result [get]
|
||||
func (h *FAQHandler) GetLastImportResult(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
kbID := secutils.SanitizeForLog(c.Param("id"))
|
||||
|
||||
result, err := h.knowledgeService.GetLastFAQImportResult(ctx, kbID)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, nil)
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": nil,
|
||||
"message": "暂无导入记录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": result,
|
||||
})
|
||||
}
|
||||
|
||||
// updateLastFAQImportResultDisplayStatusRequest is the request payload for UpdateLastImportResultDisplayStatus
|
||||
type updateLastFAQImportResultDisplayStatusRequest struct {
|
||||
DisplayStatus string `json:"display_status" binding:"required,oneof=open close"`
|
||||
|
||||
@@ -194,8 +194,7 @@ func RegisterFAQRoutes(r *gin.RouterGroup, handler *handler.FAQHandler) {
|
||||
faq.PUT("/entries/tags", handler.UpdateEntryTagBatch)
|
||||
faq.DELETE("/entries", handler.DeleteEntries)
|
||||
faq.POST("/search", handler.SearchFAQ)
|
||||
// FAQ import result API
|
||||
faq.GET("/import/last-result", handler.GetLastImportResult)
|
||||
// FAQ import result display status
|
||||
faq.PUT("/import/last-result/display", handler.UpdateLastImportResultDisplayStatus)
|
||||
}
|
||||
// FAQ import progress route (outside of knowledge-base scope)
|
||||
|
||||
@@ -293,6 +293,7 @@ const (
|
||||
)
|
||||
|
||||
// FAQImportProgress represents the progress of an FAQ import task stored in Redis
|
||||
// When Status is "completed", the result fields (SkippedCount, ImportMode, ImportedAt, DisplayStatus, ProcessingTime) are populated.
|
||||
type FAQImportProgress struct {
|
||||
TaskID string `json:"task_id"` // UUID for the import task
|
||||
KBID string `json:"kb_id"` // Knowledge Base ID
|
||||
@@ -303,6 +304,7 @@ type FAQImportProgress struct {
|
||||
Processed int `json:"processed"` // Entries processed so far
|
||||
SuccessCount int `json:"success_count"` // 成功导入/验证通过的条目数
|
||||
FailedCount int `json:"failed_count"` // 失败的条目数
|
||||
SkippedCount int `json:"skipped_count,omitempty"` // 跳过的条目数(如重复等)
|
||||
FailedEntries []FAQFailedEntry `json:"failed_entries,omitempty"` // 失败条目详情(少量时直接返回)
|
||||
FailedEntriesURL string `json:"failed_entries_url,omitempty"` // 失败条目CSV下载URL(大量时返回URL)
|
||||
Message string `json:"message"` // Status message
|
||||
@@ -310,6 +312,12 @@ type FAQImportProgress struct {
|
||||
CreatedAt int64 `json:"created_at"` // Task creation timestamp
|
||||
UpdatedAt int64 `json:"updated_at"` // Last update timestamp
|
||||
DryRun bool `json:"dry_run,omitempty"` // 是否为 dry run 模式
|
||||
|
||||
// Result fields (populated when Status == "completed")
|
||||
ImportMode string `json:"import_mode,omitempty"` // 导入模式:append 或 replace
|
||||
ImportedAt time.Time `json:"imported_at,omitempty"` // 导入完成时间
|
||||
DisplayStatus string `json:"display_status,omitempty"` // 显示状态:open 或 close
|
||||
ProcessingTime int64 `json:"processing_time,omitempty"` // 处理耗时(毫秒)
|
||||
}
|
||||
|
||||
// FAQImportMetadata 存储在Knowledge.Metadata中的FAQ导入任务信息
|
||||
|
||||
@@ -126,8 +126,6 @@ type KnowledgeService interface {
|
||||
SaveKBCloneProgress(ctx context.Context, progress *types.KBCloneProgress) error
|
||||
// GetFAQImportProgress retrieves the progress of an FAQ import task
|
||||
GetFAQImportProgress(ctx context.Context, taskID string) (*types.FAQImportProgress, error)
|
||||
// GetLastFAQImportResult retrieves the latest FAQ import result for a knowledge base
|
||||
GetLastFAQImportResult(ctx context.Context, kbID string) (*types.FAQImportResult, error)
|
||||
// UpdateLastFAQImportResultDisplayStatus updates the display status of FAQ import result
|
||||
UpdateLastFAQImportResultDisplayStatus(ctx context.Context, kbID string, displayStatus string) error
|
||||
// SearchKnowledge searches knowledge items by keyword across the tenant.
|
||||
|
||||
Reference in New Issue
Block a user