Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions frontend/src/components/CardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,15 @@ function CardView<T extends BaseCardDataType>(props: CardViewProps<T>) {
<TagsRenderer tags={Array.isArray(item?.tags) ? item.tags : []} />

{/* Description */}
<p className="text-gray-400 text-xs text-ellipsis overflow-hidden whitespace-nowrap line-clamp-2 mt-3 mb-2">
<Tooltip title={item?.description}>
<Tooltip
title={item?.description}
placement="topLeft"
getPopupContainer={(triggerNode) => triggerNode.parentElement || document.body}
>
<p className="text-gray-400 text-xs text-ellipsis overflow-hidden whitespace-nowrap line-clamp-2 mt-3 mb-2">
{item?.description}
</Tooltip>
</p>
</p>
</Tooltip>

{/* Statistics */}
<div className="grid grid-cols-2 gap-4 py-2">
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/DetailHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ interface OperationItem {
onMenuClick?: (key: string) => void;
onClick?: () => void;
danger?: boolean;
confirm?: {
title: string;
description?: string;
cancelText?: string;
okText?: string;
okType?: "default" | "primary" | "danger";
onConfirm?: () => void;
};
}

interface TagConfig {
Expand All @@ -36,6 +44,7 @@ interface DetailHeaderProps<T> {
statistics: StatisticItem[];
operations: OperationItem[];
tagConfig?: TagConfig;
titleExtra?: React.ReactNode;
}

// 标签单行渲染组件
Expand Down Expand Up @@ -214,6 +223,7 @@ function DetailHeader<T>({
statistics,
operations,
tagConfig,
titleExtra,
}: DetailHeaderProps<T>): React.ReactNode {
return (
<Card>
Expand All @@ -236,6 +246,7 @@ function DetailHeader<T>({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-1">
<h1 className="text-lg font-bold text-gray-900 truncate">{(data as any)?.name}</h1>
{titleExtra}
{(data as any)?.status && (
<Tag color={(data as any).status?.color} className="shrink-0">
<div className="flex items-center gap-2 text-xs">
Expand Down
71 changes: 59 additions & 12 deletions frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type React from "react";
import { useEffect, useState } from "react";
import { Table, Badge, Button, Breadcrumb, Tooltip, App, Card, Input, Empty, Spin } from "antd";
import { Table, Badge, Button, Breadcrumb, Tooltip, App, Card, Input, Empty, Spin, Tag } from "antd";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
Expand All @@ -15,7 +15,7 @@ import { useNavigate, useParams } from "react-router";
import DetailHeader from "@/components/DetailHeader";
import { SearchControls } from "@/components/SearchControls";
import { KBFile, KnowledgeBaseItem, KnowledgeGraphNode, KnowledgeGraphEdge, KBType } from "../knowledge-base.model";
import { mapFileData, mapKnowledgeBase } from "../knowledge-base.const";
import { getKBTypeMap, mapFileData, mapKnowledgeBase } from "../knowledge-base.const";
import {
deleteKnowledgeBaseByIdUsingDelete,
deleteKnowledgeBaseFileByIdUsingDelete,
Expand All @@ -30,11 +30,7 @@ import CreateKnowledgeBase from "../components/CreateKnowledgeBase";
import KnowledgeGraphView, { GraphEntitySelection } from "../components/KnowledgeGraphView";
import { useTranslation } from "react-i18next";

interface StatisticItem {
icon?: React.ReactNode;
label: string;
value: string | number;
}
type HeaderStatisticItem = React.ComponentProps<typeof DetailHeader>["statistics"][number];
// Use UnifiedSearchResult from model - flat structure from backend
// Backend returns: { id, text, score, metadata, resultType, knowledgeBaseId, knowledgeBaseName }
interface RecallResult {
Expand Down Expand Up @@ -75,6 +71,37 @@ const KnowledgeBaseDetailPage: React.FC = () => {
const [graphData, setGraphData] = useState<{ nodes: KnowledgeGraphNode[]; edges: KnowledgeGraphEdge[] }>({ nodes: [], edges: [] });
const [graphSelection, setGraphSelection] = useState<GraphEntitySelection | null>(null);

const kbTypeMap = getKBTypeMap(t);
const kbTypeMeta = knowledgeBase ? kbTypeMap[knowledgeBase.type as KBType] : undefined;

const detailHeaderData = knowledgeBase
? {
...knowledgeBase,
tags: (() => {
const rawTags = Array.isArray((knowledgeBase as any)?.tags) ? ((knowledgeBase as any).tags as any[]) : [];
const normalized = rawTags
.map((tag) => {
if (!tag) return "";
if (typeof tag === "string") return tag;
return String(tag.label ?? tag.name ?? "");
})
.map((s) => s.trim())
.filter(Boolean);

const typeLabel = String(kbTypeMeta?.tag?.label ?? "").trim();
const filtered = typeLabel ? normalized.filter((label) => label !== typeLabel) : normalized;

return Array.from(new Set(filtered));
})(),
description:
knowledgeBase.description && knowledgeBase.description.trim().length > 0
? knowledgeBase.description
: kbTypeMap[knowledgeBase.type as KBType]?.description ??
kbTypeMap[knowledgeBase.type as KBType]?.label ??
knowledgeBase.description,
}
: knowledgeBase;

const fetchKnowledgeBaseDetails = async (id: string) => {
const { data } = await queryKnowledgeBaseByIdUsingGet(id);
setKnowledgeBase(mapKnowledgeBase(data, true, t));
Expand Down Expand Up @@ -156,7 +183,8 @@ const KnowledgeBaseDetailPage: React.FC = () => {
threshold: 0.2,
knowledgeBaseIds: [knowledgeBase.id],
});
setRecallResults(result?.data || []);
const data = Array.isArray(result) ? result : (result as any)?.data;
setRecallResults(Array.isArray(data) ? data : []);
} catch {
setRecallResults([]);
}
Expand Down Expand Up @@ -330,9 +358,23 @@ const KnowledgeBaseDetailPage: React.FC = () => {
</Breadcrumb>
</div>
<DetailHeader
data={knowledgeBase}
statistics={knowledgeBase && Array.isArray((knowledgeBase as { statistics?: StatisticItem[] }).statistics)
? ((knowledgeBase as { statistics?: StatisticItem[] }).statistics ?? [])
data={detailHeaderData}
titleExtra={
kbTypeMeta?.tag?.label ? (
<Tag
className="shrink-0"
style={{
background: kbTypeMeta.tag.background,
color: kbTypeMeta.tag.color,
borderColor: kbTypeMeta.tag.background,
}}
>
{kbTypeMeta.tag.label}
</Tag>
) : null
}
statistics={knowledgeBase && Array.isArray((knowledgeBase as { statistics?: HeaderStatisticItem[] }).statistics)
? ((knowledgeBase as { statistics?: HeaderStatisticItem[] }).statistics ?? [])
: []}
operations={operations}
/>
Expand Down Expand Up @@ -457,7 +499,12 @@ const KnowledgeBaseDetailPage: React.FC = () => {
searchPlaceholder={t("knowledgeBase.detail.searchPlaceholder")}
filters={[]}
onFiltersChange={handleFiltersChange}
onClearFilters={() => setSearchParams({ ...searchParams, filter: { type: [], status: [], tags: [] } })}
onClearFilters={() =>
setSearchParams({
...searchParams,
filter: { type: [], status: [], tags: [], categories: [], selectedStar: false },
})
}
showViewToggle={false}
showReload={false}
/>
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {

const [selectedFilesMap, setSelectedFilesMap] = useState({});

const toBackendProcessType = (uiValue: string) => {
switch (uiValue) {
case "FIXED_LENGTH_CHUNK":
return "LENGTH_CHUNK";
case "CHAPTER_CHUNK":
// 后端暂不支持 CHAPTER_CHUNK:用段落分块策略兼容“按章节分块”的入口
return "PARAGRAPH_CHUNK";
default:
return uiValue;
}
};

// 定义分块选项
const sliceOptions = [
{ label: t("knowledgeBase.const.sliceMethod.default"), value: "DEFAULT_CHUNK" },
Expand Down Expand Up @@ -124,7 +136,7 @@ export default function AddDataDialog({ knowledgeBase, onDataAdded }) {
// 构造符合API要求的请求数据
const requestData = {
files: Object.values(selectedFilesMap),
processType: newKB.processType,
processType: toBackendProcessType(newKB.processType),
chunkSize: Number(newKB.chunkSize), // 确保是数字类型
overlapSize: Number(newKB.overlapSize), // 确保是数字类型
delimiter: newKB.delimiter,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/KnowledgeBase/knowledge-base.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function retrieveKnowledgeBaseContent(data: {
threshold?: number;
knowledgeBaseIds: string[];
}): Promise<UnifiedSearchResult[]> {
return post("/api/knowledge-base/retrieve", data);
return post("/api/knowledge-base/v2/retrieve", data);
}

// 获取知识库文件详情(分页的切片数据)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
实现知识库相关的 REST API 接口。
对应 Java: com.datamate.rag.indexer.interfaces.KnowledgeBaseController
"""
import json

from fastapi import APIRouter, Depends, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession

Expand Down Expand Up @@ -153,6 +155,32 @@ async def get_file_chunks(
async def retrieve_knowledge_base(
request: RetrieveReq,
db: AsyncSession = Depends(get_db),
):
"""检索知识库内容(统一检索接口,兼容旧版本格式)"""
service = UnifiedRetrievalService(db)
results = await service.search(request)

legacy_results = []
for item in results:
legacy_item = {
"entity": {
"metadata": json.dumps(item.get("metadata", {}), ensure_ascii=False),
"text": item.get("text", ""),
"id": item.get("id", ""),
},
"score": item.get("score", 0.0),
"id": item.get("id", ""),
"knowledgeBaseId": item.get("knowledgeBaseId", ""),
"knowledgeBaseName": item.get("knowledgeBaseName", ""),
}
legacy_results.append(legacy_item)

return SuccessResponse(data=legacy_results)

@router.post("/v2/retrieve", response_model=SuccessResponse)
async def v2_retrieve_knowledge_base(
request: RetrieveReq,
db: AsyncSession = Depends(get_db),
):
"""检索知识库内容(统一检索接口)"""
service = UnifiedRetrievalService(db)
Expand All @@ -166,7 +194,7 @@ async def query_knowledge_base(
db: AsyncSession = Depends(get_db),
):
"""查询知识库(支持向量检索和知识图谱)

根据知识库类型自动选择查询策略:
- DOCUMENT: 向量检索
- GRAPH: 知识图谱查询
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from app.core.exception import BusinessError, ErrorCodes
from app.db.models.dataset_management import DatasetFiles
from app.db.models.knowledge_gen import KnowledgeBase, RagFile, FileStatus
from app.db.models.knowledge_gen import KnowledgeBase, RagFile, FileStatus, RagType
from app.db.models.models import Models
from app.module.rag.infra.vectorstore import drop_collection, rename_collection, delete_chunks_by_rag_file_ids
from app.module.rag.repository import KnowledgeBaseRepository, RagFileRepository
Expand Down Expand Up @@ -88,15 +88,23 @@ async def update(self, knowledge_base_id: str, request: KnowledgeBaseUpdateReq)
if not knowledge_base:
raise BusinessError(ErrorCodes.RAG_KNOWLEDGE_BASE_NOT_FOUND)

old_name = knowledge_base.name
old_name = str(knowledge_base.name)
new_name = request.name
kb_type = knowledge_base.type

knowledge_base.name = request.name
knowledge_base.description = request.description

await self.kb_repo.update(knowledge_base)

if old_name != request.name:
if old_name != new_name:
try:
rename_collection(old_name, request.name)
if kb_type == RagType.DOCUMENT.value:
rename_collection(old_name, new_name)
elif kb_type == RagType.GRAPH.value:
from app.module.rag.service.strategy.graph_strategy import GraphKnowledgeBaseStrategy
GraphKnowledgeBaseStrategy.rename_workspace(old_name, new_name)
GraphKnowledgeBaseStrategy.clear_cache(old_name)
except BusinessError:
await self.db.rollback()
raise
Expand All @@ -113,13 +121,30 @@ async def delete(self, knowledge_base_id: str) -> None:
if not knowledge_base:
raise BusinessError(ErrorCodes.RAG_KNOWLEDGE_BASE_NOT_FOUND)

kb_name = str(knowledge_base.name)
kb_type = knowledge_base.type

await self.file_repo.delete_by_knowledge_base(knowledge_base_id)
await self.kb_repo.delete(knowledge_base_id)

try:
drop_collection(knowledge_base.name)
except Exception as e:
logger.error("删除 Milvus 集合失败: %s", e)
if kb_type == RagType.DOCUMENT.value:
try:
drop_collection(kb_name)
except Exception as e:
logger.error("删除 Milvus 集合失败: %s", e)
elif kb_type == RagType.GRAPH.value:
try:
from app.module.rag.service.strategy.graph_strategy import GraphKnowledgeBaseStrategy
import shutil
from pathlib import Path
from app.core.config import settings
workspace_path = Path(settings.rag_storage_dir) / kb_name
if workspace_path.exists():
shutil.rmtree(workspace_path)
logger.info("已删除知识图谱 workspace: %s", kb_name)
GraphKnowledgeBaseStrategy.clear_cache(kb_name)
except Exception as e:
logger.error("删除知识图谱 workspace 失败: %s", e)

await self.db.commit()

Expand All @@ -141,7 +166,8 @@ async def get_by_id(self, knowledge_base_id: str) -> KnowledgeBaseResp:
})
return KnowledgeBaseResp(**data)

def _kb_to_dict(self, kb: KnowledgeBase) -> dict:
@staticmethod
def _kb_to_dict(kb: KnowledgeBase) -> dict:
"""知识库实体转字典"""
return {
"id": kb.id,
Expand Down
Loading
Loading