diff --git a/astrbot/core/knowledge_base/kb_db_sqlite.py b/astrbot/core/knowledge_base/kb_db_sqlite.py index 6a2cb5e0a8..ada9793a99 100644 --- a/astrbot/core/knowledge_base/kb_db_sqlite.py +++ b/astrbot/core/knowledge_base/kb_db_sqlite.py @@ -219,25 +219,45 @@ async def list_documents_by_kb( kb_id: str, offset: int = 0, limit: int = 100, + search: str | None = None, ) -> list[KBDocument]: - """列出知识库的所有文档""" + """List documents in a knowledge base. + + Args: + kb_id: Knowledge base ID. + offset: Number of documents to skip. + limit: Maximum number of documents to return. + search: Optional partial match on document name; disabled when None or empty. + + Returns: + List of matching KBDocument rows. + """ async with self.get_db() as session: + stmt = select(KBDocument).where(col(KBDocument.kb_id) == kb_id) + if search: + stmt = stmt.where(col(KBDocument.doc_name).contains(search)) stmt = ( - select(KBDocument) - .where(col(KBDocument.kb_id) == kb_id) - .offset(offset) - .limit(limit) - .order_by(desc(KBDocument.created_at)) + stmt.offset(offset).limit(limit).order_by(desc(KBDocument.created_at)) ) result = await session.execute(stmt) return list(result.scalars().all()) - async def count_documents_by_kb(self, kb_id: str) -> int: - """统计知识库的文档数量""" + async def count_documents_by_kb(self, kb_id: str, search: str | None = None) -> int: + """Count documents in a knowledge base. + + Args: + kb_id: Knowledge base ID. + search: Optional partial match on document name; disabled when None or empty. + + Returns: + Total number of matching documents. + """ async with self.get_db() as session: stmt = select(func.count(col(KBDocument.id))).where( col(KBDocument.kb_id) == kb_id, ) + if search: + stmt = stmt.where(col(KBDocument.doc_name).contains(search)) result = await session.execute(stmt) return result.scalar() or 0 diff --git a/astrbot/core/knowledge_base/kb_helper.py b/astrbot/core/knowledge_base/kb_helper.py index c29e45876d..bcd42d1126 100644 --- a/astrbot/core/knowledge_base/kb_helper.py +++ b/astrbot/core/knowledge_base/kb_helper.py @@ -469,11 +469,37 @@ async def list_documents( self, offset: int = 0, limit: int = 100, + search: str | None = None, ) -> list[KBDocument]: - """列出知识库的所有文档""" - docs = await self.kb_db.list_documents_by_kb(self.kb.kb_id, offset, limit) + """List documents in the knowledge base. + + Args: + offset: Number of documents to skip. + limit: Maximum number of documents to return. + search: Optional partial match on document name; disabled when None or empty. + + Returns: + List of matching KBDocument rows. + """ + docs = await self.kb_db.list_documents_by_kb( + self.kb.kb_id, + offset, + limit, + search=search, + ) return docs + async def count_documents(self, search: str | None = None) -> int: + """Count documents in the knowledge base. + + Args: + search: Optional partial match on document name; disabled when None or empty. + + Returns: + Total number of matching documents. + """ + return await self.kb_db.count_documents_by_kb(self.kb.kb_id, search=search) + async def get_document(self, doc_id: str) -> KBDocument | None: """获取单个文档""" doc = await self.kb_db.get_document_by_id(doc_id) diff --git a/astrbot/dashboard/api/knowledge_bases.py b/astrbot/dashboard/api/knowledge_bases.py index c6f62235dd..595f6ff911 100644 --- a/astrbot/dashboard/api/knowledge_bases.py +++ b/astrbot/dashboard/api/knowledge_bases.py @@ -182,6 +182,7 @@ async def list_knowledge_base_documents( kb_id=kb_id, page=_to_int(request.query_params.get("page"), 1), page_size=_to_int(request.query_params.get("page_size"), 100), + search=request.query_params.get("search"), ), prefix="获取文档列表失败", ) @@ -390,6 +391,7 @@ async def dashboard_list_documents( kb_id=request.query_params.get("kb_id"), page=_to_int(request.query_params.get("page"), 1), page_size=_to_int(request.query_params.get("page_size"), 100), + search=request.query_params.get("search"), ), prefix="获取文档列表失败", ) diff --git a/astrbot/dashboard/services/knowledge_base_service.py b/astrbot/dashboard/services/knowledge_base_service.py index c7f9546418..ec162aa299 100644 --- a/astrbot/dashboard/services/knowledge_base_service.py +++ b/astrbot/dashboard/services/knowledge_base_service.py @@ -266,16 +266,24 @@ async def background_import_task( async def list_kbs(self, *, page: int, page_size: int) -> dict[str, Any]: kb_manager = self.get_kb_manager() kbs = await kb_manager.list_kbs() + total = len(kbs) + + # Clamp page and page_size to at least 1 before calculating offsets/slices. + page = max(page, 1) + page_size = max(page_size, 1) + start = (page - 1) * page_size + end = start + page_size + paged_kbs = kbs[start:end] kb_list = [] - for kb in kbs: + for kb in paged_kbs: kb_dict = kb.model_dump() kb_helper = await kb_manager.get_kb(kb.kb_id) if kb_helper and kb_helper.init_error: kb_dict["init_error"] = kb_helper.init_error kb_list.append(kb_dict) - return {"items": kb_list, "page": page, "page_size": page_size} + return {"items": kb_list, "page": page, "page_size": page_size, "total": total} async def list_kbs_from_dashboard_query(self, *, page, page_size) -> dict[str, Any]: return await self.list_kbs( @@ -437,6 +445,7 @@ async def list_documents( kb_id: str | None, page: int, page_size: int, + search: str | None = None, ) -> dict[str, Any]: if not kb_id: raise KnowledgeBaseServiceError("缺少参数 kb_id") @@ -444,12 +453,25 @@ async def list_documents( if not kb_helper: raise KnowledgeBaseServiceError("知识库不存在") + if search is not None: + search = search.strip() + if not search: + search = None + + page = max(page, 1) + page_size = max(page_size, 1) offset = (page - 1) * page_size - doc_list = await kb_helper.list_documents(offset=offset, limit=page_size) + doc_list = await kb_helper.list_documents( + offset=offset, + limit=page_size, + search=search, + ) + total = await kb_helper.count_documents(search=search) return { "items": [doc.model_dump() for doc in doc_list], "page": page, "page_size": page_size, + "total": total, } async def list_documents_from_dashboard_query( @@ -458,11 +480,13 @@ async def list_documents_from_dashboard_query( kb_id: str | None, page, page_size, + search: str | None = None, ) -> dict[str, Any]: return await self.list_documents( kb_id=kb_id, page=self._to_int(page, 1), page_size=self._to_int(page_size, 100), + search=search, ) async def upload_document( diff --git a/dashboard/src/api/generated/openapi-v1/types.gen.ts b/dashboard/src/api/generated/openapi-v1/types.gen.ts index b865e34371..abd4474889 100644 --- a/dashboard/src/api/generated/openapi-v1/types.gen.ts +++ b/dashboard/src/api/generated/openapi-v1/types.gen.ts @@ -2640,6 +2640,10 @@ export type ListKnowledgeDocumentsData = { query?: { page?: number; page_size?: number; + /** + * Filter documents by name (case-insensitive partial match). + */ + search?: string; }; }; diff --git a/dashboard/src/api/v1.ts b/dashboard/src/api/v1.ts index cf505abd14..62d181dd74 100644 --- a/dashboard/src/api/v1.ts +++ b/dashboard/src/api/v1.ts @@ -1378,7 +1378,7 @@ export const knowledgeApi = { openApiV1.deleteKnowledgeBase({ path: { kb_id: kbId } }), ); }, - documents(kbId: string, params?: { page?: number; page_size?: number }) { + documents(kbId: string, params?: { page?: number; page_size?: number; search?: string }) { return typed( openApiV1.listKnowledgeDocuments({ path: { kb_id: kbId }, diff --git a/dashboard/src/views/knowledge-base/KBList.vue b/dashboard/src/views/knowledge-base/KBList.vue index d25b8e458a..17989528ed 100644 --- a/dashboard/src/views/knowledge-base/KBList.vue +++ b/dashboard/src/views/knowledge-base/KBList.vue @@ -79,6 +79,15 @@ + + @@ -269,6 +278,9 @@ const loading = ref(false) const saving = ref(false) const deleting = ref(false) const kbList = ref([]) +const page = ref(1) +const pageSize = ref(20) +const total = ref(0) const embeddingProviders = ref([]) const rerankProviders = ref([]) const originalEmbeddingProvider = ref(null) @@ -324,18 +336,18 @@ const emojiCategories = [ const loadKnowledgeBases = async (refreshStats = false) => { loading.value = true try { - const params: any = {} if (refreshStats) { - params.refresh_stats = 'true' + page.value = 1 } - const response = await knowledgeApi.list({ - page: params.page, - page_size: params.page_size, - refresh_stats: params.refresh_stats === 'true' + page: page.value, + page_size: pageSize.value, + refresh_stats: refreshStats }) if (response.data.status === 'ok') { - kbList.value = response.data.data.items || [] + const data = response.data.data + kbList.value = data.items || [] + total.value = data.total || 0 } else { showSnackbar(response.data.message || t('messages.loadError'), 'error') } @@ -407,7 +419,9 @@ const deleteKB = async () => { if (response.data.status === 'ok') { showSnackbar(t('messages.deleteSuccess')) - // 先刷新列表,再关闭对话框 + if (kbList.value.length === 1 && page.value > 1) { + page.value -= 1 + } await loadKnowledgeBases() showDeleteDialog.value = false deleteTarget.value = null diff --git a/dashboard/src/views/knowledge-base/components/DocumentsTab.vue b/dashboard/src/views/knowledge-base/components/DocumentsTab.vue index 29a49d0da7..7769ada8f9 100644 --- a/dashboard/src/views/knowledge-base/components/DocumentsTab.vue +++ b/dashboard/src/views/knowledge-base/components/DocumentsTab.vue @@ -11,7 +11,9 @@ - + - + @@ -236,7 +238,7 @@