Преглед изворни кода

feat: add pagination and sort by created_at desc for knowledge list

guantao пре 3 часа
родитељ
комит
210e75eb81
1 измењених фајлова са 71 додато и 8 уклоњено
  1. 71 8
      knowhub/server.py

+ 71 - 8
knowhub/server.py

@@ -725,13 +725,14 @@ async def save_knowledge(knowledge: KnowledgeIn):
 
 @app.get("/api/knowledge")
 def list_knowledge(
-    limit: int = Query(default=100, ge=1, le=1000),
+    page: int = Query(default=1, ge=1),
+    page_size: int = Query(default=20, ge=1, le=100),
     types: Optional[str] = None,
     scopes: Optional[str] = None,
     owner: Optional[str] = None,
     tags: Optional[str] = None
 ):
-    """列出知识(支持后端筛选)"""
+    """列出知识(支持后端筛选和分页)"""
     try:
         # 构建过滤表达式
         filters = []
@@ -757,13 +758,33 @@ def list_knowledge(
         # 如果没有过滤条件,查询所有
         filter_expr = ' and '.join(filters) if filters else 'id != ""'
 
-        # 查询 Milvus
-        results = milvus_store.query(filter_expr, limit=limit)
+        # 查询 Milvus(先获取所有符合条件的数据)
+        # Milvus 的 limit 是总数限制,我们需要获取足够多的数据来支持分页
+        max_limit = 10000  # 设置一个合理的上限
+        results = milvus_store.query(filter_expr, limit=max_limit)
 
         # 转换为可序列化的格式
         serialized_results = [serialize_milvus_result(r) for r in results]
 
-        return {"results": serialized_results, "count": len(serialized_results)}
+        # 按 created_at 降序排序(最新的在前)
+        serialized_results.sort(key=lambda x: x.get('created_at', 0), reverse=True)
+
+        # 计算分页
+        total = len(serialized_results)
+        total_pages = (total + page_size - 1) // page_size  # 向上取整
+        start_idx = (page - 1) * page_size
+        end_idx = start_idx + page_size
+        page_results = serialized_results[start_idx:end_idx]
+
+        return {
+            "results": page_results,
+            "pagination": {
+                "page": page,
+                "page_size": page_size,
+                "total": total,
+                "total_pages": total_pages
+            }
+        }
 
     except Exception as e:
         print(f"[List Knowledge] 错误: {e}")
@@ -1431,6 +1452,17 @@ def frontend():
 
         <!-- 知识列表 -->
         <div id="knowledgeList" class="space-y-4"></div>
+
+        <!-- 分页控件 -->
+        <div id="pagination" class="flex justify-center items-center gap-4 mt-6 hidden">
+            <button onclick="goToPage(currentPage - 1)" id="prevBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded disabled:opacity-50 disabled:cursor-not-allowed">
+                上一页
+            </button>
+            <span id="pageInfo" class="text-gray-700"></span>
+            <button onclick="goToPage(currentPage + 1)" id="nextBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded disabled:opacity-50 disabled:cursor-not-allowed">
+                下一页
+            </button>
+        </div>
     </div>
 
     <!-- 新增/编辑 Modal -->
@@ -1481,6 +1513,10 @@ def frontend():
     <script>
         let allKnowledge = [];
         let availableTags = [];
+        let currentPage = 1;
+        let pageSize = 20;
+        let totalPages = 1;
+        let totalCount = 0;
 
         async function loadTags() {
             const res = await fetch('/api/knowledge/meta/tags');
@@ -1500,9 +1536,10 @@ def frontend():
             ).join('');
         }
 
-        async function loadKnowledge() {
+        async function loadKnowledge(page = 1) {
             const params = new URLSearchParams();
-            params.append('limit', '1000');
+            params.append('page', page);
+            params.append('page_size', pageSize);
 
             const selectedTypes = Array.from(document.querySelectorAll('.type-filter:checked')).map(el => el.value);
             if (selectedTypes.length > 0) {
@@ -1533,7 +1570,11 @@ def frontend():
                 }
                 const data = await res.json();
                 allKnowledge = data.results || [];
+                currentPage = data.pagination.page;
+                totalPages = data.pagination.total_pages;
+                totalCount = data.pagination.total;
                 renderKnowledge(allKnowledge);
+                updatePagination();
             } catch (error) {
                 console.error('加载错误:', error);
                 document.getElementById('knowledgeList').innerHTML = '<p class="text-red-500 text-center py-8">加载错误: ' + error.message + '</p>';
@@ -1541,7 +1582,29 @@ def frontend():
         }
 
         function applyFilters() {
-            loadKnowledge();
+            currentPage = 1;  // 重置到第一页
+            loadKnowledge(currentPage);
+        }
+
+        function goToPage(page) {
+            if (page < 1 || page > totalPages) return;
+            loadKnowledge(page);
+        }
+
+        function updatePagination() {
+            const paginationDiv = document.getElementById('pagination');
+            const pageInfo = document.getElementById('pageInfo');
+            const prevBtn = document.getElementById('prevBtn');
+            const nextBtn = document.getElementById('nextBtn');
+
+            if (totalPages <= 1) {
+                paginationDiv.classList.add('hidden');
+            } else {
+                paginationDiv.classList.remove('hidden');
+                pageInfo.textContent = `第 ${currentPage} / ${totalPages} 页 (共 ${totalCount} 条)`;
+                prevBtn.disabled = currentPage === 1;
+                nextBtn.disabled = currentPage === totalPages;
+            }
         }
 
         function renderKnowledge(list) {