diff --git a/internal/builders/knowledge_builder.go b/internal/builders/knowledge_builder.go index cf9a094..b532231 100644 --- a/internal/builders/knowledge_builder.go +++ b/internal/builders/knowledge_builder.go @@ -54,6 +54,26 @@ func BuildKnowledgeDocument(item *models.KnowledgeDocument) response.KnowledgeDo } } +func BuildKnowledgeDocumentList(item *models.KnowledgeDocument) response.KnowledgeDocumentListResponse { + return response.KnowledgeDocumentListResponse{ + ID: item.ID, + KnowledgeBaseID: item.KnowledgeBaseID, + Title: item.Title, + Status: item.Status, + StatusName: enums.GetStatusLabel(item.Status), + IndexStatus: item.IndexStatus, + IndexStatusName: enums.GetKnowledgeDocumentIndexStatusLabel(item.IndexStatus), + IndexedAt: item.IndexedAt, + IndexError: item.IndexError, + ContentHash: item.ContentHash, + ContentType: item.ContentType, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + CreateUserName: item.CreateUserName, + UpdateUserName: item.UpdateUserName, + } +} + func BuildKnowledgeFAQ(item *models.KnowledgeFAQ) response.KnowledgeFAQResponse { return response.KnowledgeFAQResponse{ ID: item.ID, diff --git a/internal/controllers/dashboard/knowledge_document_controller.go b/internal/controllers/dashboard/knowledge_document_controller.go index 674f6d8..e0f1c78 100644 --- a/internal/controllers/dashboard/knowledge_document_controller.go +++ b/internal/controllers/dashboard/knowledge_document_controller.go @@ -39,10 +39,10 @@ func (c *KnowledgeDocumentController) AnyList() *web.JsonResult { cnd.Where("index_status = ?", indexStatus) } - list, paging := services.KnowledgeDocumentService.FindPageByCnd(cnd) - results := make([]response.KnowledgeDocumentResponse, 0, len(list)) + list, paging := services.KnowledgeDocumentService.FindPageListByCnd(cnd) + results := make([]response.KnowledgeDocumentListResponse, 0, len(list)) for _, item := range list { - results = append(results, builders.BuildKnowledgeDocument(&item)) + results = append(results, builders.BuildKnowledgeDocumentList(&item)) } return web.JsonData(&web.PageResult{Results: results, Page: paging}) } diff --git a/internal/pkg/dto/response/knowledge_response.go b/internal/pkg/dto/response/knowledge_response.go index a22cd8c..3fe99ec 100644 --- a/internal/pkg/dto/response/knowledge_response.go +++ b/internal/pkg/dto/response/knowledge_response.go @@ -51,6 +51,25 @@ type KnowledgeDocumentResponse struct { UpdateUserName string `json:"updateUserName"` } +type KnowledgeDocumentListResponse struct { + ID int64 `json:"id"` + KnowledgeBaseID int64 `json:"knowledgeBaseId"` + KnowledgeBaseName string `json:"knowledgeBaseName,omitempty"` + Title string `json:"title"` + ContentType enums.KnowledgeDocumentContentType `json:"contentType"` + Status enums.Status `json:"status"` + StatusName string `json:"statusName"` + IndexStatus enums.KnowledgeDocumentIndexStatus `json:"indexStatus"` + IndexStatusName string `json:"indexStatusName"` + IndexedAt *time.Time `json:"indexedAt"` + IndexError string `json:"indexError"` + ContentHash string `json:"contentHash"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + CreateUserName string `json:"createUserName"` + UpdateUserName string `json:"updateUserName"` +} + type KnowledgeFAQResponse struct { ID int64 `json:"id"` KnowledgeBaseID int64 `json:"knowledgeBaseId"` diff --git a/internal/repositories/knowledge_document_repository.go b/internal/repositories/knowledge_document_repository.go index d4724dd..a1388d7 100644 --- a/internal/repositories/knowledge_document_repository.go +++ b/internal/repositories/knowledge_document_repository.go @@ -62,6 +62,18 @@ func (r *knowledgeDocumentRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) return } +func (r *knowledgeDocumentRepository) FindPageListByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.KnowledgeDocument, paging *sqls.Paging) { + cnd.Find(db.Omit("content"), &list) + count := cnd.Count(db, &models.KnowledgeDocument{}) + + paging = &sqls.Paging{ + Page: cnd.Paging.Page, + Limit: cnd.Paging.Limit, + Total: count, + } + return +} + func (r *knowledgeDocumentRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 { return cnd.Count(db, &models.KnowledgeDocument{}) } diff --git a/internal/repositories/knowledge_document_repository_test.go b/internal/repositories/knowledge_document_repository_test.go new file mode 100644 index 0000000..f8d5294 --- /dev/null +++ b/internal/repositories/knowledge_document_repository_test.go @@ -0,0 +1,55 @@ +package repositories + +import ( + "fmt" + "strings" + "testing" + + "cs-agent/internal/models" + "cs-agent/internal/pkg/enums" + + "github.com/glebarez/sqlite" + "github.com/mlogclub/simple/sqls" + "gorm.io/gorm" +) + +func TestKnowledgeDocumentRepositoryFindPageListByCndOmitsContentAndPaginates(t *testing.T) { + db, err := gorm.Open(sqlite.Open("file:knowledge_document_repository_test?mode=memory&cache=shared"), &gorm.Config{}) + if err != nil { + t.Fatalf("open sqlite error = %v", err) + } + if err := db.AutoMigrate(&models.KnowledgeDocument{}); err != nil { + t.Fatalf("auto migrate error = %v", err) + } + if err := db.Exec("DELETE FROM knowledge_documents").Error; err != nil { + t.Fatalf("clean knowledge documents error = %v", err) + } + sqls.SetDB(db) + + for i := 1; i <= 3; i++ { + item := &models.KnowledgeDocument{ + KnowledgeBaseID: 1, + Title: fmt.Sprintf("doc-%d", i), + ContentType: enums.KnowledgeDocumentContentTypeMarkdown, + Content: strings.Repeat("large document content ", 200), + Status: enums.StatusOk, + IndexStatus: enums.KnowledgeDocumentIndexStatusPending, + } + if err := db.Create(item).Error; err != nil { + t.Fatalf("create document %d error = %v", i, err) + } + } + + list, paging := KnowledgeDocumentRepository.FindPageListByCnd(db, sqls.NewCnd().Eq("knowledge_base_id", 1).Asc("id").Page(1, 2)) + if len(list) != 2 { + t.Fatalf("len(list) = %d, want 2", len(list)) + } + if paging.Total != 3 { + t.Fatalf("paging.Total = %d, want 3", paging.Total) + } + for _, item := range list { + if item.Content != "" { + t.Fatalf("list item content should be empty, got %q", item.Content) + } + } +} diff --git a/internal/services/knowledge_document_service.go b/internal/services/knowledge_document_service.go index d7b472d..b3e8454 100644 --- a/internal/services/knowledge_document_service.go +++ b/internal/services/knowledge_document_service.go @@ -54,6 +54,10 @@ func (s *knowledgeDocumentService) FindPageByCnd(cnd *sqls.Cnd) (list []models.K return repositories.KnowledgeDocumentRepository.FindPageByCnd(sqls.DB(), cnd) } +func (s *knowledgeDocumentService) FindPageListByCnd(cnd *sqls.Cnd) (list []models.KnowledgeDocument, paging *sqls.Paging) { + return repositories.KnowledgeDocumentRepository.FindPageListByCnd(sqls.DB(), cnd) +} + func (s *knowledgeDocumentService) Count(cnd *sqls.Cnd) int64 { return repositories.KnowledgeDocumentRepository.Count(sqls.DB(), cnd) } diff --git a/web/app/dashboard/knowledge/_components/document-list.tsx b/web/app/dashboard/knowledge/_components/document-list.tsx index a2d02b7..ee58754 100644 --- a/web/app/dashboard/knowledge/_components/document-list.tsx +++ b/web/app/dashboard/knowledge/_components/document-list.tsx @@ -11,6 +11,7 @@ import { import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; +import { ListPagination } from "@/components/list-pagination"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -47,7 +48,7 @@ import { fetchKnowledgeDocuments, updateKnowledgeDocument, type CreateKnowledgeDocumentPayload, - type KnowledgeDocument, + type KnowledgeDocumentListItem, type PageResult, } from "@/lib/api/admin"; import { getEnumLabel, getEnumOptions } from "@/lib/enums"; @@ -93,7 +94,7 @@ function getIndexStatusBadgeVariant(status: string) { } } -function renderIndexStatusBadge(item: KnowledgeDocument) { +function renderIndexStatusBadge(item: KnowledgeDocumentListItem) { const badge = ( {item.indexStatusName} @@ -121,21 +122,6 @@ function renderIndexStatusBadge(item: KnowledgeDocument) { ) } -function getDocumentPreview(content: string, contentType: string) { - const preview = - contentType === "markdown" - ? content - .replace(/[`*_>#-]/g, " ") - .replace(/\[(.*?)\]\((.*?)\)/g, "$1") - .replace(/\s+/g, " ") - .trim() - : content - .replace(/<[^>]+>/g, " ") - .replace(/\s+/g, " ") - .trim(); - return preview || "暂无内容"; -} - const VIEW_MODE_STORAGE_KEY = "knowledge-document-view-mode"; export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentListProps) { @@ -145,11 +131,13 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL const [keyword, setKeyword] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [indexStatusFilter, setIndexStatusFilter] = useState("all"); + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(20); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [actionLoadingMap, setActionLoadingMap] = useState>({}); const [dialogOpen, setDialogOpen] = useState(false); - const [editingItem, setEditingItem] = useState( + const [editingItem, setEditingItem] = useState( null, ); const [viewMode, setViewMode] = useState<"list" | "grid">(() => { @@ -157,14 +145,26 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL const saved = localStorage.getItem(VIEW_MODE_STORAGE_KEY); return saved === "list" || saved === "grid" ? saved : "grid"; }); - const [documents, setDocuments] = useState>({ + const [documents, setDocuments] = useState>({ results: [], page: { page: 1, limit: 20, total: 0 }, }); - const loadData = useCallback(async () => { + const loadData = useCallback(async (options?: { + keyword?: string; + statusFilter?: string; + indexStatusFilter?: string; + page?: number; + limit?: number; + }) => { + const nextKeyword = options?.keyword ?? keyword; + const nextStatusFilter = options?.statusFilter ?? statusFilter; + const nextIndexStatusFilter = options?.indexStatusFilter ?? indexStatusFilter; + const nextPage = options?.page ?? page; + const nextLimit = options?.limit ?? limit; + if (!knowledgeBaseId) { - setDocuments({ results: [], page: { page: 1, limit: 20, total: 0 } }); + setDocuments({ results: [], page: { page: 1, limit, total: 0 } }); setLoading(false); return; } @@ -172,11 +172,12 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL setLoading(true); try { const data = await fetchKnowledgeDocuments({ - title: keyword.trim() || undefined, - status: statusFilter === "all" ? undefined : statusFilter, - indexStatus: indexStatusFilter === "all" ? undefined : indexStatusFilter, + title: nextKeyword.trim() || undefined, + status: nextStatusFilter === "all" ? undefined : nextStatusFilter, + indexStatus: nextIndexStatusFilter === "all" ? undefined : nextIndexStatusFilter, knowledgeBaseId, - limit: 1000, + page: nextPage, + limit: nextLimit, }); setDocuments(data); } catch (error) { @@ -184,7 +185,7 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL } finally { setLoading(false); } - }, [indexStatusFilter, keyword, statusFilter, knowledgeBaseId]); + }, [indexStatusFilter, keyword, statusFilter, knowledgeBaseId, limit, page]); useEffect(() => { void loadData(); @@ -207,9 +208,19 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL } function applyFilters() { - setKeyword(keywordInput); - setStatusFilter(statusFilterInput); - setIndexStatusFilter(indexStatusFilterInput); + const nextKeyword = keywordInput; + const nextStatusFilter = statusFilterInput; + const nextIndexStatusFilter = indexStatusFilterInput; + setKeyword(nextKeyword); + setStatusFilter(nextStatusFilter); + setIndexStatusFilter(nextIndexStatusFilter); + setPage(1); + void loadData({ + keyword: nextKeyword, + statusFilter: nextStatusFilter, + indexStatusFilter: nextIndexStatusFilter, + page: 1, + }); } function handleFilterKeyDown(event: React.KeyboardEvent) { @@ -239,7 +250,7 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL }); }, [onActionStateChange, loadData, openCreateDialog, viewMode, loading]); - function openEditDialog(item: KnowledgeDocument) { + function openEditDialog(item: KnowledgeDocumentListItem) { setEditingItem(item); setDialogOpen(true); } @@ -281,7 +292,7 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL } } - async function handleDelete(item: KnowledgeDocument) { + async function handleDelete(item: KnowledgeDocumentListItem) { setActionLoadingMap((prev) => ({ ...prev, [item.id]: { ...prev[item.id], delete: true } })); try { await deleteKnowledgeDocument(item.id); @@ -294,7 +305,7 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL } } - async function handleBuildIndex(item: KnowledgeDocument) { + async function handleBuildIndex(item: KnowledgeDocumentListItem) { setActionLoadingMap((prev) => ({ ...prev, [item.id]: { ...prev[item.id], rebuildIndex: true } })); try { await buildKnowledgeDocumentIndex(item.id); @@ -398,9 +409,6 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
{item.title}
{renderIndexStatusBadge(item)} -
- {getDocumentPreview(item.content, item.contentType)} -
{item.createUserName || "-"} {formatDateTime(item.createdAt)} @@ -477,9 +485,6 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
{item.title}
{renderIndexStatusBadge(item)}
-
- {getDocumentPreview(item.content, item.contentType)} -
{item.indexStatus === KnowledgeDocumentIndexStatus.Indexed ? `索引时间:${formatDateTime(item.indexedAt)}` @@ -551,6 +556,23 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
+
+ { + setPage(nextPage); + void loadData({ page: nextPage }); + }} + onLimitChange={(nextLimit) => { + setLimit(nextLimit); + setPage(1); + void loadData({ limit: nextLimit, page: 1 }); + }} + /> +
+ export type KnowledgeFAQ = { id: number knowledgeBaseId: number @@ -1579,7 +1581,7 @@ export function rebuildKnowledgeBaseIndex(id: number) { export function fetchKnowledgeDocuments( query?: Record ) { - return request>( + return request>( `/api/dashboard/knowledge-document/list${toQueryString(query)}` ) }