From 01b17177c8bdcd14d84be5a7f569bb433217b938 Mon Sep 17 00:00:00 2001 From: xianrenge <34586161+xianrenge@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:37:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9F=A5=E8=AF=86=E5=BA=93=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=88=97=E8=A1=A8=E6=A0=87=E9=A2=98=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E3=80=8C=E6=B8=85=E7=A9=BA=E3=80=8D=E6=8C=89=E9=92=AE=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=B8=80=E9=94=AE=E6=B8=85=E7=A9=BA=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=88=E5=88=86=E5=9D=97=E4=B8=8E=E6=A3=80?= =?UTF-8?q?=E7=B4=A2=E7=B4=A2=E5=BC=95=E7=BA=A7=E8=81=94=E5=88=A0=E9=99=A4?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E6=B8=85=E7=A9=BA=E5=89=8D=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E8=AD=A6=E5=91=8A=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/commands.rs | 10 +++++++++ apps/desktop/src/lib.rs | 1 + crates/core/src/knowledge_base.rs | 35 ++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 2 +- docs/FEATURES.md | 3 ++- ui/src/api.ts | 2 ++ ui/src/pages/KnowledgeBasePage.tsx | 33 ++++++++++++++++++++++++++-- 7 files changed, 82 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/commands.rs b/apps/desktop/src/commands.rs index 248a80a..8881ba1 100644 --- a/apps/desktop/src/commands.rs +++ b/apps/desktop/src/commands.rs @@ -1668,6 +1668,16 @@ pub fn remove_kb_document(state: State<'_, App>, document_id: String) -> Result< kb_db::delete_document(&db, &document_id).map_err(|e| e.to_string()) } +/// 清空知识库内的全部文档(分块与检索索引一并删除)。 +#[tauri::command] +pub fn kb_clear_documents(state: State<'_, App>, kb_id: String) -> Result { + let db = state.core.db.lock().unwrap(); + kb_db::get_knowledge_base(&db, &kb_id) + .map_err(|e| e.to_string())? + .ok_or_else(|| "知识库不存在".to_string())?; + kb_db::delete_all_documents(&db, &kb_id).map_err(|e| e.to_string()) +} + /// 按知识库最新分块设置重新切分指定文档。 #[tauri::command] pub fn kb_rechunk_document( diff --git a/apps/desktop/src/lib.rs b/apps/desktop/src/lib.rs index 4603d9c..4a97059 100644 --- a/apps/desktop/src/lib.rs +++ b/apps/desktop/src/lib.rs @@ -194,6 +194,7 @@ pub fn run() { commands::get_kb_document, commands::kb_import_documents, commands::remove_kb_document, + commands::kb_clear_documents, commands::kb_rechunk_document, commands::kb_search, commands::kb_list_sources, diff --git a/crates/core/src/knowledge_base.rs b/crates/core/src/knowledge_base.rs index b76f583..7dc0eca 100644 --- a/crates/core/src/knowledge_base.rs +++ b/crates/core/src/knowledge_base.rs @@ -250,6 +250,15 @@ pub fn delete_document(db: &Connection, id: &str) -> Result<()> { Ok(()) } +/// 清空某知识库的全部文档(分块随外键级联删除,FTS 索引由触发器同步)。 +pub fn delete_all_documents(db: &Connection, kb_id: &str) -> Result { + let n = db.execute( + "DELETE FROM kb_documents WHERE kb_id = ?1", + params![kb_id], + )?; + Ok(n as i64) +} + /// 同一知识库下是否已存在来自该文件路径的文档(目录导入去重)。 pub fn document_exists(db: &Connection, kb_id: &str, file_path: &str) -> Result { if file_path.is_empty() { @@ -608,6 +617,32 @@ mod tests { assert!(hits.is_empty()); } + #[test] + fn clear_all_documents_removes_everything() { + let conn = test_conn(); + insert_knowledge_base( + &conn, + &KnowledgeBase { + id: "kb1".to_string(), + name: "测试库".to_string(), + description: String::new(), + chunk_size: 200, + chunk_overlap: 20, + doc_count: 0, + chunk_count: 0, + created_at: String::new(), + updated_at: String::new(), + }, + ) + .unwrap(); + insert_doc(&conn, "d1", "第一份文档内容,用于清空测试。"); + insert_doc(&conn, "d2", "第二份文档内容,同样会被清空。"); + let n = delete_all_documents(&conn, "kb1").unwrap(); + assert_eq!(n, 2); + assert!(list_documents(&conn, "kb1").unwrap().is_empty()); + assert!(search(&conn, Some("kb1"), "清空", 10, 0).unwrap().is_empty()); + } + #[test] fn rechunk_changes_chunk_count() { let conn = test_conn(); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ba0b320..7ed235b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -110,7 +110,7 @@ ui/src/ | 工作流 | `list_workflows`、`add_workflow`、`update_workflow`、`remove_workflow`、`set_workflow_enabled`、`restore_preset_workflows`、`run_workflow` | | 定时计划 | `list_scheduled_tasks`、`add_scheduled_task`、`update_scheduled_task`、`remove_scheduled_task`、`set_scheduled_task_enabled`、`run_scheduled_task_now` | | 邮件 | `mail_test`(用当前 SMTP 配置发送测试邮件) | -| 知识库 | `list_knowledge_bases`、`add_knowledge_base`、`update_knowledge_base`、`remove_knowledge_base`、`list_kb_documents`(分页)、`get_kb_document`、`kb_import_documents`、`remove_kb_document`、`kb_rechunk_document`、`kb_search`(分页) | +| 知识库 | `list_knowledge_bases`、`add_knowledge_base`、`update_knowledge_base`、`remove_knowledge_base`、`list_kb_documents`(分页)、`get_kb_document`、`kb_import_documents`、`remove_kb_document`、`kb_clear_documents`、`kb_rechunk_document`、`kb_search`(分页) | | 知识库·目录来源 | `kb_list_sources`、`kb_add_source`、`kb_scan_sources`(后台扫描,`kb://scan-progress` 事件推送进度)、`kb_remove_source` | | 应用/设置 | `app_info`、`autostart_status`、`autostart_set`、`settings_get`、`settings_set` | | 模型 | `list_models`、`import_model`、`remove_model`、`set_model_enabled`、`scan_models`、`add_remote_model` | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 7d83fe2..1946775 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -140,7 +140,7 @@ - **类型过滤**:只收录**可读文本**(提取内容并分块检索)、**图片 / 视频**、**音频**三类文件(图片视频音频以文件名为内容登记,可按文件名检索),检测到其他类型自动忽略; - 同一目录重复添加自动更新配置不产生重复来源;按文件路径去重,可一键「重新扫描」增量导入新增文件;删除目录来源会同时删除它导入的文档。 - **扫描进度**:添加目录 / 重新扫描时显示进度条与处理统计(处理 X / Y 个文件、成功、失败、忽略、分块数),失败文件在结尾汇总提示。 -- **文档操作**:文档列表每页 10 条,带分页控件;展示类型 / 大小 / 字数 / 分块数;支持全文预览、「重切」(按最新分块设置重新切分,适用于修改设置后)与删除。 +- **文档操作**:文档列表每页 10 条,带分页控件;展示类型 / 大小 / 字数 / 分块数;支持全文预览、「重切」(按最新分块设置重新切分,适用于修改设置后)、单条删除;标题「文档(共 x 个)」右侧提供「清空」按钮,一键清空该知识库全部文档(含分块与检索索引),清空前弹窗警告确认。 - **检索**:全文检索基于 SQLite FTS5(trigram 分词,对中文友好),按 BM25 相关性排序;支持单库检索(也可全库检索),结果每页 10 条带分页控件,展示来源文档与分块序号,可一键复制分块内容用于对话。 - **页面布局**:知识库主区内自上而下依次为「目录来源」→「文档列表」→「检索」。 - 数据表:`knowledge_bases`、`kb_documents`(含提取后的纯文本)、`kb_chunks`(分块)、`kb_chunks_fts`(FTS5 全文索引,contentless-delete 模式 + 触发器同步)。 @@ -232,6 +232,7 @@ ### 2026-08-17 +- 知识库文档列表标题增加「清空」按钮:一键清空当前知识库全部文档(分块与索引级联删除),清空前弹出警告确认。 - 知识库页调整:文档列表与检索结果增加分页控件;目录来源、文档列表区块移到检索上方;目录扫描增加进度条与处理统计(处理/成功/失败/忽略/分块数,事件驱动实时刷新)。 - 修复:知识库「添加目录 → 浏览…」无响应——为 dialog 插件在 capabilities 中补充权限(`dialog:default`),并让选择器异常在前端可见。 - 知识库「添加目录」弹窗支持两种方式选择目录:手动输入路径,或点击「浏览…」调起系统原生文件夹选择器(接入 tauri-plugin-dialog)。 diff --git a/ui/src/api.ts b/ui/src/api.ts index a1e7d7a..7ef715c 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -526,6 +526,8 @@ export const api = { ) => invoke("kb_import_documents", { kbId, files }), removeKbDocument: (documentId: string) => invoke("remove_kb_document", { documentId }), + kbClearDocuments: (kbId: string) => + invoke("kb_clear_documents", { kbId }), kbRechunkDocument: (documentId: string) => invoke("kb_rechunk_document", { documentId }), kbSearch: (kbId: string | null, query: string, page?: number, pageSize?: number) => diff --git a/ui/src/pages/KnowledgeBasePage.tsx b/ui/src/pages/KnowledgeBasePage.tsx index 5a87b81..f58d41a 100644 --- a/ui/src/pages/KnowledgeBasePage.tsx +++ b/ui/src/pages/KnowledgeBasePage.tsx @@ -253,6 +253,25 @@ export default function KnowledgeBasePage() { } } + async function handleClearDocs() { + if (!selected) return; + if ( + !confirm( + `确定清空知识库「${selected.name}」的全部 ${docTotal} 个文档吗?\n\n此操作不可恢复,所有文档、分块与检索索引将一并删除。`, + ) + ) { + return; + } + try { + const n = await api.kbClearDocuments(selected.id); + setMsg(`已清空 ${n} 个文档`); + await loadDocPage(selected.id, 1); + await refreshKbs(); + } catch (e) { + setMsg(`清空失败:${String(e)}`); + } + } + async function handleSearch(page = 1) { if (!query.trim()) return; setSearching(true); @@ -553,8 +572,18 @@ export default function KnowledgeBasePage() { {/* 文档列表 */}
-
- 文档(共 {docTotal} 个) +
+
+ 文档(共 {docTotal} 个) +
+ {docTotal > 0 ? ( + + ) : null}
{docs.length === 0 ? (