+
{currentChunks.map((chunk) => (
-
-
-
-
-
-
{t("knowledgeBase.fileDetail.messages.chunkLabel")} {chunk.id}
- {/* 算子名:从 metadata.sliceOperator 显示 */}
- {chunk.metadata?.sliceOperator && (
-
- {chunk.metadata.sliceOperator}
-
- )}
-
-
- {editingChunk === chunk.id ? (
- <>
-
-
- >
- ) : (
- <>
-
-
-
- >
- )}
-
-
-
- {editingChunk === chunk.id ? (
- setEditChunkContent(e.target.value)}
- rows={3}
- />
- ) : (
- chunk.text
- )}
-
- {/* 元数据展示,保持和召回结果风格一致 */}
-
-
{t("knowledgeBase.fileDetail.modal.metadata")}:
-
- {typeof chunk.metadata === "string"
- ? chunk.metadata
- : JSON.stringify(chunk.metadata ?? {}, null, 2)}
-
-
- {/* 结构化元数据的快捷标签(若可用) */}
-
- {chunk?.metadata?.position && {t("knowledgeBase.fileDetail.columns.position")}: {chunk.metadata.position}}
- {chunk?.metadata?.tokens && Token: {chunk.metadata.tokens}}
- {chunk?.metadata?.page && {t("knowledgeBase.fileDetail.columns.page")}: {chunk.metadata.page}}
- {chunk?.metadata?.section && {t("knowledgeBase.fileDetail.columns.section")}: {chunk.metadata.section}}
-
+
+ {t("knowledgeBase.fileDetail.messages.chunkLabel")} {chunk.id}
+ {chunk.metadata?.sliceOperator && (
+
+ {chunk.metadata.sliceOperator}
+
+ )}
+
+ }
+ extra={
+
+ {editingChunk === chunk.id ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+
+ >
+ )}
+ }
+ style={{ wordBreak: "break-all" }}
+ >
+
+ {editingChunk === chunk.id ? (
+ setEditChunkContent(e.target.value)}
+ rows={3}
+ />
+ ) : (
+ chunk.text
+ )}
+
+
+ metadata
{typeof chunk.metadata === "string" ? chunk.metadata : JSON.stringify(chunk.metadata ?? {}, null, 2)}
))}
{!loading && currentChunks.length === 0 && (
-
+
+
+
)}
From f743c6a89e834d93c2bc19b365413264d4b0d9bf Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Mon, 16 Mar 2026 11:47:09 +0800
Subject: [PATCH 04/15] fix: enhance chunk display messages in
KnowledgeBaseFileDetail
---
frontend/src/i18n/locales/en/common.json | 2 ++
frontend/src/i18n/locales/zh/common.json | 2 ++
.../pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx | 3 +--
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json
index 01ec80f5..55905895 100644
--- a/frontend/src/i18n/locales/en/common.json
+++ b/frontend/src/i18n/locales/en/common.json
@@ -1694,6 +1694,8 @@
"messages": {
"noChunks": "No chunk data",
"chunkCount": "chunks",
+ "totalChunks": "Total {{count}} chunks",
+ "showingRange": "showing {{start}}-{{end}}",
"ready": "Ready",
"previousPage": "Previous Page",
"nextPage": "Next Page",
diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json
index d51b54be..b3e9618d 100644
--- a/frontend/src/i18n/locales/zh/common.json
+++ b/frontend/src/i18n/locales/zh/common.json
@@ -1694,6 +1694,8 @@
"messages": {
"noChunks": "暂无分块数据",
"chunkCount": "个分块",
+ "totalChunks": "共 {{count}} 个分块",
+ "showingRange": "当前显示第 {{start}}-{{end}} 个",
"ready": "就绪",
"previousPage": "上一页",
"nextPage": "下一页",
diff --git a/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx b/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
index c7fd8c25..b5fd4417 100644
--- a/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
+++ b/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
@@ -118,8 +118,7 @@ const KnowledgeBaseFileDetail: React.FC = () => {
{error &&
}
- 共 {totalElements} 个分块,当前显示第 {totalElements === 0 ? 0 : (currentPage - 1) * pageSize + 1}-
- {totalElements === 0 ? 0 : Math.min(currentPage * pageSize, totalElements)} 个
+ {t("knowledgeBase.fileDetail.messages.totalChunks", { count: totalElements })},{t("knowledgeBase.fileDetail.messages.showingRange", { start: totalElements === 0 ? 0 : (currentPage - 1) * pageSize + 1, end: totalElements === 0 ? 0 : Math.min(currentPage * pageSize, totalElements) })}
);
};
From 36c9337b231e53f1407ff2cab10ab50fbd06e226 Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Mon, 16 Mar 2026 21:38:13 +0800
Subject: [PATCH 10/15] fix: enhance metadata validation and add action buttons
in KnowledgeBaseFileDetail
---
frontend/src/i18n/locales/en/common.json | 9 +-
frontend/src/i18n/locales/zh/common.json | 9 +-
.../FileDetail/KnowledgeBaseFileDetail.tsx | 259 +++++++++++++-----
3 files changed, 207 insertions(+), 70 deletions(-)
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json
index d0d59f2b..8189324c 100644
--- a/frontend/src/i18n/locales/en/common.json
+++ b/frontend/src/i18n/locales/en/common.json
@@ -1692,7 +1692,10 @@
"save": "Save",
"cancel": "Cancel",
"confirm": "Confirm",
- "formatJson": "Format"
+ "formatJson": "Format",
+ "view": "View",
+ "edit": "Edit",
+ "delete": "Delete"
},
"messages": {
"noChunks": "No chunk data",
@@ -1712,7 +1715,9 @@
"invalidMetadataFormat": "Metadata is not valid JSON format",
"textRequired": "Chunk content cannot be empty",
"formatSuccess": "JSON formatted successfully",
- "metadataHint": "Please enter valid JSON format. Click 'Format' button to auto-format."
+ "metadataHint": "Please enter valid JSON format. Click 'Format' button to auto-format.",
+ "jsonValid": "Valid JSON",
+ "jsonInvalid": "Invalid JSON"
},
"placeholders": {
"chunkContent": "Enter chunk content",
diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json
index 4f527e04..55e5fe50 100644
--- a/frontend/src/i18n/locales/zh/common.json
+++ b/frontend/src/i18n/locales/zh/common.json
@@ -1691,7 +1691,10 @@
"save": "保存",
"cancel": "取消",
"confirm": "确认",
- "formatJson": "格式化"
+ "formatJson": "格式化",
+ "view": "查看",
+ "edit": "编辑",
+ "delete": "删除"
},
"messages": {
"noChunks": "暂无分块数据",
@@ -1711,7 +1714,9 @@
"invalidMetadataFormat": "元数据不是有效的JSON格式",
"textRequired": "分块内容不能为空",
"formatSuccess": "JSON格式化成功",
- "metadataHint": "请输入有效的JSON格式数据,点击\"格式化\"按钮可自动整理格式"
+ "metadataHint": "请输入有效的JSON格式数据,点击\"格式化\"按钮可自动整理格式",
+ "jsonValid": "JSON格式正确",
+ "jsonInvalid": "JSON格式错误"
},
"placeholders": {
"chunkContent": "请输入分块内容",
diff --git a/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx b/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
index 465641ed..61b9f2a8 100644
--- a/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
+++ b/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
@@ -1,6 +1,6 @@
-import React, { useEffect, useState } from "react";
-import {Eye, Edit, Trash2, FileBox, ChevronLeft, ChevronRight} from "lucide-react";
-import { Card, Button, Badge, Input, Tabs, Modal, Breadcrumb, Tag, Spin, Empty, Alert, message } from "antd";
+import React, { useEffect, useState, useCallback } from "react";
+import {Eye, Edit, Trash2, FileBox, ChevronLeft, ChevronRight, Code, CheckCircle, AlertCircle, Wand2} from "lucide-react";
+import { Card, Button, Badge, Input, Tabs, Modal, Breadcrumb, Tag, Spin, Empty, Alert, message, Tooltip } from "antd";
import { queryKnowledgeBaseFileDetailUsingGet, updateKnowledgeBaseChunk, deleteKnowledgeBaseChunk } from "@/pages/KnowledgeBase/knowledge-base.api";
import { Link, useParams } from "react-router";
import DetailHeader from "@/components/DetailHeader";
@@ -38,6 +38,8 @@ const KnowledgeBaseFileDetail: React.FC = () => {
const [editingChunk, setEditingChunk] = useState
(null);
const [editChunkContent, setEditChunkContent] = useState("");
const [editChunkMetadata, setEditChunkMetadata] = useState("");
+ const [metadataValid, setMetadataValid] = useState(true);
+ const [metadataError, setMetadataError] = useState(null);
const [chunkDetailModal, setChunkDetailModal] = useState(null);
const [showSliceTraceDialog, setShowSliceTraceDialog] = useState(null);
const [deleteConfirmModal, setDeleteConfirmModal] = useState(null);
@@ -99,11 +101,56 @@ const KnowledgeBaseFileDetail: React.FC = () => {
setEditingChunk(chunkId);
setEditChunkContent(content);
setEditChunkMetadata(JSON.stringify(metadata ?? {}, null, 2));
+ setMetadataValid(true);
+ setMetadataError(null);
};
+ const validateJson = useCallback((value: string): { valid: boolean; error: string | null } => {
+ if (!value || value.trim() === "") {
+ return { valid: true, error: null };
+ }
+ try {
+ JSON.parse(value);
+ return { valid: true, error: null };
+ } catch (e) {
+ const errorMessage = e instanceof Error ? e.message : t("knowledgeBase.fileDetail.messages.invalidMetadataFormat");
+ return { valid: false, error: errorMessage };
+ }
+ }, [t]);
+
+ const handleMetadataChange = useCallback((value: string) => {
+ setEditChunkMetadata(value);
+ const { valid, error } = validateJson(value);
+ setMetadataValid(valid);
+ setMetadataError(error);
+ }, [validateJson]);
+
+ const formatJson = useCallback(() => {
+ try {
+ const parsed = JSON.parse(editChunkMetadata || "{}");
+ const formatted = JSON.stringify(parsed, null, 2);
+ setEditChunkMetadata(formatted);
+ setMetadataValid(true);
+ setMetadataError(null);
+ message.success(t("knowledgeBase.fileDetail.messages.formatSuccess"));
+ } catch {
+ message.error(t("knowledgeBase.fileDetail.messages.invalidMetadataFormat"));
+ }
+ }, [editChunkMetadata, t]);
+
const handleSaveChunk = async (chunkId: string) => {
if (!knowledgeBaseId) return;
-
+
+ if (!editChunkContent.trim()) {
+ message.error(t("knowledgeBase.fileDetail.messages.textRequired"));
+ return;
+ }
+
+ if (!metadataValid) {
+ message.error(t("knowledgeBase.fileDetail.messages.invalidMetadata"));
+ return;
+ }
+
let parsedMetadata = {};
try {
parsedMetadata = editChunkMetadata ? JSON.parse(editChunkMetadata) : {};
@@ -122,10 +169,12 @@ const KnowledgeBaseFileDetail: React.FC = () => {
setEditingChunk(null);
setEditChunkContent("");
setEditChunkMetadata("");
+ setMetadataValid(true);
+ setMetadataError(null);
fetchChunks(currentPage);
} catch (err: unknown) {
- const msg = typeof err === "object" && err !== null && "message" in err
- ? String((err as { message?: string }).message)
+ const msg = typeof err === "object" && err !== null && "message" in err
+ ? String((err as { message?: string }).message)
: t("knowledgeBase.fileDetail.messages.updateFailed");
message.error(msg);
} finally {
@@ -197,72 +246,34 @@ const KnowledgeBaseFileDetail: React.FC = () => {
}
extra={
- {editingChunk === chunk.id ? (
- <>
- handleSaveChunk(chunk.id)}
- loading={saving}
- >
- {t("knowledgeBase.fileDetail.actions.save")}
-
- {
- setEditingChunk(null);
- setEditChunkContent("");
- setEditChunkMetadata("");
- }}
- >
- {t("knowledgeBase.fileDetail.actions.cancel")}
-
- >
- ) : (
- <>
- handleViewChunkDetail(chunk.id)}>
-
-
- handleEditChunk(chunk.id, chunk.text, chunk.metadata)}>
-
-
- setDeleteConfirmModal(chunk.id)}>
-
-
- >
- )}
+
+ handleViewChunkDetail(chunk.id)}>
+
+
+
+
+ handleEditChunk(chunk.id, chunk.text, chunk.metadata)}>
+
+
+
+
+ setDeleteConfirmModal(chunk.id)}>
+
+
+
}
style={{ wordBreak: "break-all" }}
>
- {editingChunk === chunk.id ? (
- <>
-
+
+ metadata
+
+ {typeof chunk.metadata === "string" ? chunk.metadata : JSON.stringify(chunk.metadata ?? {}, null, 2)}
+
- {editingChunk !== chunk.id && (
-
- metadata
{typeof chunk.metadata === "string" ? chunk.metadata : JSON.stringify(chunk.metadata ?? {}, null, 2)}
-
- )}
))}
{!loading && currentChunks.length === 0 && (
@@ -394,6 +405,122 @@ const KnowledgeBaseFileDetail: React.FC = () => {
>
{t("knowledgeBase.fileDetail.modal.deleteConfirmMessage")}
+
+ {
+ setEditingChunk(null);
+ setEditChunkContent("");
+ setEditChunkMetadata("");
+ setMetadataValid(true);
+ setMetadataError(null);
+ }}
+ footer={null}
+ title={
+
+
+ {t("knowledgeBase.fileDetail.modal.editChunkTitle")} - {editingChunk}
+
+ }
+ width={900}
+ destroyOnClose
+ >
+
+
+
+
+
+
setEditChunkContent(e.target.value)}
+ rows={6}
+ placeholder={t("knowledgeBase.fileDetail.placeholders.chunkContent")}
+ className="font-mono"
+ style={{
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
+ }}
+ />
+
+
+
+
+
+
+ {metadataValid ? (
+
+
+ {t("knowledgeBase.fileDetail.messages.jsonValid")}
+
+ ) : (
+
+
+ {t("knowledgeBase.fileDetail.messages.jsonInvalid")}
+
+ )}
+
}
+ onClick={formatJson}
+ type="default"
+ >
+ {t("knowledgeBase.fileDetail.actions.formatJson")}
+
+
+
+
+
handleMetadataChange(e.target.value)}
+ rows={10}
+ placeholder={t("knowledgeBase.fileDetail.placeholders.metadata")}
+ className="font-mono"
+ style={{
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
+ borderColor: metadataValid ? '#d9d9d9' : '#ff4d4f',
+ }}
+ status={metadataValid ? undefined : 'error'}
+ />
+ {metadataError && (
+
+ )}
+
+ {t("knowledgeBase.fileDetail.messages.metadataHint")}
+
+
+
+
+
+ {
+ setEditingChunk(null);
+ setEditChunkContent("");
+ setEditChunkMetadata("");
+ setMetadataValid(true);
+ setMetadataError(null);
+ }}
+ >
+ {t("knowledgeBase.fileDetail.actions.cancel")}
+
+ editingChunk && handleSaveChunk(editingChunk)}
+ loading={saving}
+ disabled={!metadataValid || !editChunkContent.trim()}
+ icon={}
+ >
+ {t("knowledgeBase.fileDetail.actions.save")}
+
+
+
+
);
};
From 39a717e38a23077bd749a16abe11e309af59ab09 Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Mon, 16 Mar 2026 23:54:24 +0800
Subject: [PATCH 11/15] fix: enhance chunk deletion process and return
rag_file_id in delete_chunk_by_id
---
.../Detail/KnowledgeBaseDetail.tsx | 1 +
.../app/module/rag/infra/vectorstore/store.py | 22 ++++++++++--
.../rag/service/knowledge_base_service.py | 35 +++++++++++--------
3 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx b/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
index 2b7c387a..9812b924 100644
--- a/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
+++ b/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
@@ -154,6 +154,7 @@ const KnowledgeBaseDetailPage: React.FC = () => {
});
message.success(t("knowledgeBase.detail.messages.fileDeleted"));
fetchFiles();
+ fetchKnowledgeBaseDetails(knowledgeBase!.id);
} catch {
message.error(t("knowledgeBase.detail.messages.fileDeleteFailed"));
}
diff --git a/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py b/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py
index e0972d61..88fb49c8 100644
--- a/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py
+++ b/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py
@@ -310,20 +310,38 @@ def update_chunk_by_id(
raise BusinessError(ErrorCodes.RAG_MILVUS_ERROR, f"更新分块失败: {str(e)}") from e
-def delete_chunk_by_id(collection_name: str, chunk_id: str) -> None:
+def delete_chunk_by_id(collection_name: str, chunk_id: str) -> Optional[str]:
"""删除指定 ID 的分块
Args:
collection_name: 集合名称
chunk_id: 分块 ID
+
+ Returns:
+ 被删除分块对应的 rag_file_id(如果存在),否则返回 None
"""
try:
client = get_milvus_client()
filter_expr = f'id == "{chunk_id}"'
+
+ # 先查询 chunk 的 metadata 获取 rag_file_id
+ existing = client.query(
+ collection_name=collection_name,
+ filter=filter_expr,
+ output_fields=["metadata"],
+ )
+
+ rag_file_id = None
+ if existing:
+ metadata = existing[0].get("metadata", {})
+ rag_file_id = metadata.get("rag_file_id")
+
client.delete(collection_name=collection_name, filter=filter_expr)
- logger.info("成功删除分块: collection=%s chunk_id=%s", collection_name, chunk_id)
+ logger.info("成功删除分块: collection=%s chunk_id=%s rag_file_id=%s", collection_name, chunk_id, rag_file_id)
+
+ return rag_file_id
except Exception as e:
logger.error("删除分块失败: %s", e)
diff --git a/runtime/datamate-python/app/module/rag/service/knowledge_base_service.py b/runtime/datamate-python/app/module/rag/service/knowledge_base_service.py
index 22f27333..99027ce6 100644
--- a/runtime/datamate-python/app/module/rag/service/knowledge_base_service.py
+++ b/runtime/datamate-python/app/module/rag/service/knowledge_base_service.py
@@ -16,6 +16,7 @@
from app.db.models.dataset_management import DatasetFiles
from app.db.models.knowledge_gen import KnowledgeBase, RagFile, FileStatus, RagType
from app.db.models.models import Models
+from app.module.rag.infra.embeddings import EmbeddingFactory
from app.module.rag.infra.vectorstore import (
drop_collection,
rename_collection,
@@ -23,7 +24,6 @@
update_chunk_by_id,
delete_chunk_by_id,
)
-from app.module.rag.infra.embeddings import EmbeddingFactory
from app.module.rag.repository import KnowledgeBaseRepository, RagFileRepository
from app.module.rag.schema.request import (
KnowledgeBaseCreateReq,
@@ -404,7 +404,7 @@ async def update_chunk(
metadata: dict = None,
) -> None:
"""更新指定分块的文本和元数据
-
+
Args:
knowledge_base_id: 知识库 ID
chunk_id: 分块 ID
@@ -414,27 +414,26 @@ async def update_chunk(
knowledge_base = await self.kb_repo.get_by_id(knowledge_base_id)
if not knowledge_base:
raise BusinessError(ErrorCodes.RAG_KNOWLEDGE_BASE_NOT_FOUND)
-
+
if knowledge_base.type != RagType.DOCUMENT.value:
raise BusinessError(
ErrorCodes.RAG_INVALID_REQUEST,
f"知识库类型 {knowledge_base.type} 不支持分块更新"
)
-
- from app.module.rag.service.strategy.vector_strategy import VectorKnowledgeBaseStrategy
+
from app.module.system.service.common_service import get_model_by_id
import asyncio
-
+
embedding_entity = await get_model_by_id(self.db, knowledge_base.embedding_model)
if not embedding_entity:
raise BusinessError(ErrorCodes.RAG_MODEL_NOT_FOUND)
-
+
embedding = EmbeddingFactory.create_embeddings(
model_name=str(embedding_entity.model_name),
base_url=getattr(embedding_entity, "base_url", None),
api_key=getattr(embedding_entity, "api_key", None),
)
-
+
await asyncio.to_thread(
update_chunk_by_id,
collection_name=str(knowledge_base.name),
@@ -443,19 +442,19 @@ async def update_chunk(
metadata=metadata,
embedding_instance=embedding,
)
-
+
logger.info(
"成功更新分块: kb=%s chunk_id=%s",
knowledge_base_id, chunk_id
)
-
+
async def delete_chunk(
self,
knowledge_base_id: str,
chunk_id: str,
) -> None:
"""删除指定分块
-
+
Args:
knowledge_base_id: 知识库 ID
chunk_id: 分块 ID
@@ -463,20 +462,26 @@ async def delete_chunk(
knowledge_base = await self.kb_repo.get_by_id(knowledge_base_id)
if not knowledge_base:
raise BusinessError(ErrorCodes.RAG_KNOWLEDGE_BASE_NOT_FOUND)
-
+
if knowledge_base.type != RagType.DOCUMENT.value:
raise BusinessError(
ErrorCodes.RAG_INVALID_REQUEST,
f"知识库类型 {knowledge_base.type} 不支持分块删除"
)
-
+
import asyncio
- await asyncio.to_thread(
+ rag_file_id = await asyncio.to_thread(
delete_chunk_by_id,
collection_name=str(knowledge_base.name),
chunk_id=chunk_id,
)
-
+
+ if rag_file_id:
+ rag_file = await self.file_repo.get_by_id(rag_file_id)
+ if rag_file and rag_file.chunk_count and rag_file.chunk_count > 0:
+ rag_file.chunk_count = rag_file.chunk_count - 1
+ await self.db.commit()
+
logger.info(
"成功删除分块: kb=%s chunk_id=%s",
knowledge_base_id, chunk_id
From dc7745129202ffc0c0561bf0ddaaf54eeb328a14 Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Tue, 17 Mar 2026 00:05:22 +0800
Subject: [PATCH 12/15] fix: implement batched chunk deletion by rag_file_id in
store.py
---
.../app/module/rag/infra/vectorstore/store.py | 80 ++++++++++++++-----
1 file changed, 58 insertions(+), 22 deletions(-)
diff --git a/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py b/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py
index 88fb49c8..58ae56ba 100644
--- a/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py
+++ b/runtime/datamate-python/app/module/rag/infra/vectorstore/store.py
@@ -13,7 +13,6 @@
"""
from __future__ import annotations
-import json
import logging
from typing import List, Optional
@@ -27,6 +26,47 @@
logger = logging.getLogger(__name__)
+BATCH_DELETE_SIZE = 100
+
+
+def _delete_chunks_by_rag_file_id_batched(client, collection_name: str, rag_file_id: str) -> int:
+ """分批删除指定 rag_file_id 的所有 chunks
+
+ Args:
+ client: Milvus 客户端
+ collection_name: 集合名称
+ rag_file_id: RAG 文件 ID
+
+ Returns:
+ 删除的总数量
+ """
+ filter_expr = f'metadata["rag_file_id"] == "{rag_file_id}"'
+ total_deleted = 0
+
+ while True:
+ try:
+ results = client.query(
+ collection_name=collection_name,
+ filter=filter_expr,
+ output_fields=["id"],
+ limit=BATCH_DELETE_SIZE,
+ )
+ if not results:
+ break
+
+ chunk_ids = [r["id"] for r in results]
+ id_filter = ' || '.join([f'id == "{cid}"' for cid in chunk_ids])
+ client.delete(collection_name=collection_name, filter=f"({id_filter})")
+ total_deleted += len(chunk_ids)
+
+ if len(chunk_ids) < BATCH_DELETE_SIZE:
+ break
+ except Exception as e:
+ logger.warning("分批删除失败: collection=%s rag_file_id=%s error=%s", collection_name, rag_file_id, e)
+ break
+
+ return total_deleted
+
def drop_collection(collection_name: str) -> None:
"""删除 Milvus 集合
@@ -201,12 +241,8 @@ def delete_chunks_by_rag_file_ids(collection_name: str, rag_file_ids: List[str])
client = get_milvus_client()
for rid in rag_file_ids:
- json_value = json.dumps({"rag_file_id": rid})
- filter_expr = f'JSON_CONTAINS(metadata, \'{json_value}\')'
- try:
- client.delete(collection_name=collection_name, filter=filter_expr)
- except Exception as del_err:
- logger.warning("删除分块时部分失败: collection=%s rag_file_id=%s: %s", collection_name, rid, del_err)
+ deleted = _delete_chunks_by_rag_file_id_batched(client, collection_name, rid)
+ logger.info("删除文件分块: collection=%s rag_file_id=%s deleted=%d", collection_name, rid, deleted)
logger.info("已按 rag_file_id 删除集合 %s 中的分块: %s", collection_name, rag_file_ids)
@@ -258,14 +294,14 @@ def update_chunk_by_id(
"""
try:
client = get_milvus_client()
-
+
filter_expr = f'id == "{chunk_id}"'
existing = client.query(
collection_name=collection_name,
filter=filter_expr,
output_fields=["metadata"],
)
-
+
if not existing:
raise BusinessError(
ErrorCodes.RAG_CHUNK_NOT_FOUND,
@@ -280,17 +316,17 @@ def update_chunk_by_id(
# 确保保留原有的 rag_file_id 字段,防止用户修改时丢失
if "rag_file_id" in existing_metadata and "rag_file_id" not in metadata:
metadata = {**metadata, "rag_file_id": existing_metadata["rag_file_id"]}
-
+
if embedding_instance:
embedding = embedding_instance
else:
from app.module.rag.infra.embeddings import EmbeddingFactory
embedding = EmbeddingFactory.create_embeddings()
-
+
vector = embedding.embed_query(text)
-
+
client.delete(collection_name=collection_name, filter=filter_expr)
-
+
client.insert(
collection_name=collection_name,
data=[{
@@ -300,9 +336,9 @@ def update_chunk_by_id(
"vector": vector,
}]
)
-
+
logger.info("成功更新分块: collection=%s chunk_id=%s", collection_name, chunk_id)
-
+
except BusinessError:
raise
except Exception as e:
@@ -322,27 +358,27 @@ def delete_chunk_by_id(collection_name: str, chunk_id: str) -> Optional[str]:
"""
try:
client = get_milvus_client()
-
+
filter_expr = f'id == "{chunk_id}"'
-
+
# 先查询 chunk 的 metadata 获取 rag_file_id
existing = client.query(
collection_name=collection_name,
filter=filter_expr,
output_fields=["metadata"],
)
-
+
rag_file_id = None
if existing:
metadata = existing[0].get("metadata", {})
rag_file_id = metadata.get("rag_file_id")
-
+
client.delete(collection_name=collection_name, filter=filter_expr)
-
+
logger.info("成功删除分块: collection=%s chunk_id=%s rag_file_id=%s", collection_name, chunk_id, rag_file_id)
-
+
return rag_file_id
-
+
except Exception as e:
logger.error("删除分块失败: %s", e)
raise BusinessError(ErrorCodes.RAG_MILVUS_ERROR, f"删除分块失败: {str(e)}") from e
From 99b7e5ef8be0b3a2f47fe67ef6ecea88154ec34c Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Tue, 17 Mar 2026 00:20:37 +0800
Subject: [PATCH 13/15] fix: add confirmation modal for file deletion in
KnowledgeBase components
---
frontend/src/i18n/locales/en/common.json | 2 ++
frontend/src/i18n/locales/zh/common.json | 2 ++
.../KnowledgeBase/Detail/KnowledgeBaseDetail.tsx | 15 +++++++++++++--
.../FileDetail/KnowledgeBaseFileDetail.tsx | 2 ++
4 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json
index 8189324c..3bb4724f 100644
--- a/frontend/src/i18n/locales/en/common.json
+++ b/frontend/src/i18n/locales/en/common.json
@@ -1646,6 +1646,8 @@
"confirm": {
"deleteTitle": "Confirm deletion of this knowledge base?",
"deleteDescription": "It cannot be recovered after deletion. Please operate with caution.",
+ "deleteFileTitle": "Confirm deletion of this file?",
+ "deleteFileDescription": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone.",
"okText": "Delete",
"cancelText": "Cancel"
},
diff --git a/frontend/src/i18n/locales/zh/common.json b/frontend/src/i18n/locales/zh/common.json
index 55e5fe50..a8f592ff 100644
--- a/frontend/src/i18n/locales/zh/common.json
+++ b/frontend/src/i18n/locales/zh/common.json
@@ -1646,6 +1646,8 @@
"confirm": {
"deleteTitle": "确认删除该知识库吗?",
"deleteDescription": "删除后将无法恢复,请谨慎操作。",
+ "deleteFileTitle": "确认删除该文件吗?",
+ "deleteFileDescription": "确定要删除文件「{{name}}」吗?删除后将无法恢复。",
"okText": "删除",
"cancelText": "取消"
},
diff --git a/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx b/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
index 9812b924..be537f47 100644
--- a/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
+++ b/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
@@ -1,6 +1,7 @@
import type React from "react";
import { useEffect, useState } from "react";
-import { Table, Badge, Button, Breadcrumb, Tooltip, App, Card, Input, Empty, Spin, Tag } from "antd";
+import { Table, Badge, Button, Breadcrumb, Tooltip, App, Card, Input, Empty, Spin, Tag, Modal } from "antd";
+import { ExclamationCircleOutlined } from "@ant-design/icons";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
@@ -264,7 +265,17 @@ const KnowledgeBaseDetailPage: React.FC = () => {
label: t("knowledgeBase.detail.actions.deleteFile"),
icon:
,
danger: true,
- onClick: handleDeleteFile,
+ onClick: (file: KBFile) => {
+ Modal.confirm({
+ title: t("knowledgeBase.detail.confirm.deleteFileTitle"),
+ content: t("knowledgeBase.detail.confirm.deleteFileDescription", { name: file.name }),
+ okText: t("knowledgeBase.detail.confirm.okText"),
+ okType: "danger",
+ cancelText: t("knowledgeBase.detail.confirm.cancelText"),
+ centered: true,
+ onOk: () => handleDeleteFile(file),
+ });
+ },
},
];
diff --git a/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx b/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
index 61b9f2a8..5d646dcd 100644
--- a/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
+++ b/frontend/src/pages/KnowledgeBase/FileDetail/KnowledgeBaseFileDetail.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useCallback } from "react";
import {Eye, Edit, Trash2, FileBox, ChevronLeft, ChevronRight, Code, CheckCircle, AlertCircle, Wand2} from "lucide-react";
import { Card, Button, Badge, Input, Tabs, Modal, Breadcrumb, Tag, Spin, Empty, Alert, message, Tooltip } from "antd";
+import { ExclamationCircleOutlined } from "@ant-design/icons";
import { queryKnowledgeBaseFileDetailUsingGet, updateKnowledgeBaseChunk, deleteKnowledgeBaseChunk } from "@/pages/KnowledgeBase/knowledge-base.api";
import { Link, useParams } from "react-router";
import DetailHeader from "@/components/DetailHeader";
@@ -402,6 +403,7 @@ const KnowledgeBaseFileDetail: React.FC = () => {
okText={t("knowledgeBase.fileDetail.actions.confirm")}
cancelText={t("knowledgeBase.fileDetail.actions.cancel")}
okButtonProps={{ danger: true, loading: deleting }}
+ centered
>
{t("knowledgeBase.fileDetail.modal.deleteConfirmMessage")}
From a8c5e5762be087c426a7cbd3ca5e79dd530f006c Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Tue, 17 Mar 2026 00:31:00 +0800
Subject: [PATCH 14/15] fix: replace Popconfirm with Modal for confirmation in
DetailHeader actions
---
.../src/components/DeleteConfirmModal.tsx | 1 +
frontend/src/components/DetailHeader.tsx | 39 +++++++++++--------
2 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/frontend/src/components/DeleteConfirmModal.tsx b/frontend/src/components/DeleteConfirmModal.tsx
index 87f5438d..1a7a8779 100644
--- a/frontend/src/components/DeleteConfirmModal.tsx
+++ b/frontend/src/components/DeleteConfirmModal.tsx
@@ -57,6 +57,7 @@ export default function DeleteConfirmModal({
okType="danger"
okText={t("components.deleteConfirm.confirm")}
cancelText={t("components.deleteConfirm.cancel")}
+ centered
>
{defaultMessage}
diff --git a/frontend/src/components/DetailHeader.tsx b/frontend/src/components/DetailHeader.tsx
index fcf7480b..cbd33261 100644
--- a/frontend/src/components/DetailHeader.tsx
+++ b/frontend/src/components/DetailHeader.tsx
@@ -1,6 +1,6 @@
import React, { useLayoutEffect, useEffect, useRef, useState, useCallback, useMemo } from "react";
import { Database } from "lucide-react";
-import { Card, Button, Tag, Tooltip, Popconfirm, Popover } from "antd";
+import { Card, Button, Tag, Tooltip, Modal } from "antd";
import type { ItemType } from "antd/es/menu/interface";
import AddTagPopover from "./AddTagPopover";
import ActionDropdown from "./ActionDropdown";
@@ -291,23 +291,30 @@ function DetailHeader
({
);
}
if (op.confirm) {
+ const showConfirmModal = () => {
+ Modal.confirm({
+ title: op.confirm?.title,
+ content: op.confirm?.description,
+ okText: op.confirm?.okText,
+ okType: op.danger ? "danger" : "primary",
+ cancelText: op.confirm?.cancelText,
+ centered: true,
+ onOk: () => {
+ if (op.onClick) {
+ op.onClick();
+ } else {
+ op?.confirm?.onConfirm?.();
+ }
+ },
+ });
+ };
return (
- {
- if (op.onClick) {
- op.onClick()
- } else {
- op?.confirm?.onConfirm?.();
- }
- }}
- okType={op.danger ? "danger" : "primary"}
- overlayStyle={{ zIndex: 9999 }}
- >
-
-
+
);
}
From 187e02506647d5dea3ea0f3d11f5839d0901a7d4 Mon Sep 17 00:00:00 2001
From: Dallas98 <990259227@qq.com>
Date: Tue, 17 Mar 2026 00:39:06 +0800
Subject: [PATCH 15/15] fix: enhance delete confirmation message in
DatasetDetail with item name
---
frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx b/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx
index 82c89951..6010c206 100644
--- a/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx
+++ b/frontend/src/pages/DataManagement/Detail/DatasetDetail.tsx
@@ -178,7 +178,7 @@ export default function DatasetDetail() {
danger: true,
confirm: {
title: t("dataManagement.confirm.deleteDatasetTitle"),
- description: t("dataManagement.confirm.deleteDatasetDesc"),
+ description: t("dataManagement.confirm.deleteDatasetDesc", { itemName: dataset.name }),
okText: t("dataManagement.confirm.deleteConfirm"),
cancelText: t("dataManagement.confirm.deleteCancel"),
okType: "danger",